annotate layer/TimeValueLayer.cpp @ 1534:bfd8b22fd67c

Fix #1904 Scrolling colour 3d plot does not always work when in View normalisation mode. We shouldn't imagine we've just invalidated the cache if the truth is that we've only just created the renderer
author Chris Cannam
date Wed, 09 Oct 2019 13:45:17 +0100
parents 37df1530519d
children 4f8c72adbf43
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 "TimeValueLayer.h"
Chris@0 17
Chris@128 18 #include "data/model/Model.h"
Chris@0 19 #include "base/RealTime.h"
Chris@0 20 #include "base/Profiler.h"
Chris@197 21 #include "base/LogRange.h"
Chris@437 22 #include "base/RangeMapper.h"
Chris@960 23 #include "base/Pitch.h"
Chris@128 24 #include "view/View.h"
Chris@0 25
Chris@128 26 #include "data/model/SparseTimeValueModel.h"
Chris@340 27 #include "data/model/Labeller.h"
Chris@0 28
Chris@70 29 #include "widgets/ItemEditDialog.h"
Chris@125 30 #include "widgets/ListInputDialog.h"
Chris@701 31 #include "widgets/TextAbbrev.h"
Chris@70 32
Chris@1078 33 #include "ColourDatabase.h"
Chris@376 34 #include "ColourMapper.h"
Chris@691 35 #include "PianoScale.h"
Chris@698 36 #include "LinearNumericalScale.h"
Chris@698 37 #include "LogNumericalScale.h"
Chris@699 38 #include "LinearColourScale.h"
Chris@699 39 #include "LogColourScale.h"
Chris@1078 40 #include "PaintAssistant.h"
Chris@66 41
Chris@0 42 #include <QPainter>
Chris@6 43 #include <QPainterPath>
Chris@21 44 #include <QMouseEvent>
Chris@125 45 #include <QRegExp>
Chris@316 46 #include <QTextStream>
Chris@360 47 #include <QMessageBox>
Chris@340 48 #include <QInputDialog>
Chris@0 49
Chris@0 50 #include <iostream>
Chris@0 51 #include <cmath>
Chris@0 52
Chris@526 53 //#define DEBUG_TIME_VALUE_LAYER 1
Chris@526 54
Chris@44 55 TimeValueLayer::TimeValueLayer() :
Chris@287 56 SingleColourLayer(),
Chris@21 57 m_editing(false),
Chris@23 58 m_originalPoint(0, 0.0, tr("New Point")),
Chris@21 59 m_editingPoint(0, 0.0, tr("New Point")),
Chris@1408 60 m_editingCommand(nullptr),
Chris@197 61 m_colourMap(0),
Chris@1362 62 m_colourInverted(false),
Chris@66 63 m_plotStyle(PlotConnectedPoints),
Chris@437 64 m_verticalScale(AutoAlignScale),
Chris@513 65 m_drawSegmentDivisions(true),
Chris@553 66 m_derivative(false),
Chris@437 67 m_scaleMinimum(0),
Chris@437 68 m_scaleMaximum(0)
Chris@0 69 {
Chris@44 70
Chris@0 71 }
Chris@0 72
Chris@1470 73 int
Chris@1470 74 TimeValueLayer::getCompletion(LayerGeometryProvider *) const
Chris@1470 75 {
Chris@1470 76 auto model = ModelById::get(m_model);
Chris@1470 77 if (model) return model->getCompletion();
Chris@1470 78 else return 0;
Chris@1470 79 }
Chris@1470 80
Chris@0 81 void
Chris@1470 82 TimeValueLayer::setModel(ModelId modelId)
Chris@0 83 {
Chris@1471 84 auto newModel = ModelById::getAs<SparseTimeValueModel>(modelId);
Chris@1471 85
Chris@1471 86 if (!modelId.isNone() && !newModel) {
Chris@1471 87 throw std::logic_error("Not a SparseTimeValueModel");
Chris@1471 88 }
Chris@1471 89
Chris@1470 90 if (m_model == modelId) return;
Chris@1470 91 m_model = modelId;
Chris@1470 92
Chris@1471 93 if (newModel) {
Chris@1471 94
Chris@1471 95 connectSignals(m_model);
Chris@0 96
Chris@1471 97 m_scaleMinimum = 0;
Chris@1471 98 m_scaleMaximum = 0;
Chris@0 99
Chris@1471 100 if (newModel->getRDFTypeURI().endsWith("Segment")) {
Chris@1471 101 setPlotStyle(PlotSegmentation);
Chris@1471 102 }
Chris@1471 103 if (newModel->getRDFTypeURI().endsWith("Change")) {
Chris@1471 104 setPlotStyle(PlotSegmentation);
Chris@1471 105 }
Chris@494 106 }
Chris@1471 107
Chris@0 108 emit modelReplaced();
Chris@0 109 }
Chris@0 110
Chris@0 111 Layer::PropertyList
Chris@0 112 TimeValueLayer::getProperties() const
Chris@0 113 {
Chris@287 114 PropertyList list = SingleColourLayer::getProperties();
Chris@87 115 list.push_back("Plot Type");
Chris@87 116 list.push_back("Vertical Scale");
Chris@100 117 list.push_back("Scale Units");
Chris@513 118 list.push_back("Draw Segment Division Lines");
Chris@553 119 list.push_back("Show Derivative");
Chris@0 120 return list;
Chris@0 121 }
Chris@0 122
Chris@87 123 QString
Chris@87 124 TimeValueLayer::getPropertyLabel(const PropertyName &name) const
Chris@87 125 {
Chris@87 126 if (name == "Plot Type") return tr("Plot Type");
Chris@87 127 if (name == "Vertical Scale") return tr("Vertical Scale");
Chris@100 128 if (name == "Scale Units") return tr("Scale Units");
Chris@513 129 if (name == "Draw Segment Division Lines") return tr("Draw Segment Division Lines");
Chris@553 130 if (name == "Show Derivative") return tr("Show Derivative");
Chris@287 131 return SingleColourLayer::getPropertyLabel(name);
Chris@87 132 }
Chris@87 133
Chris@515 134 QString
Chris@515 135 TimeValueLayer::getPropertyIconName(const PropertyName &name) const
Chris@515 136 {
Chris@515 137 if (name == "Draw Segment Division Lines") return "lines";
Chris@553 138 if (name == "Show Derivative") return "derivative";
Chris@515 139 return "";
Chris@515 140 }
Chris@515 141
Chris@0 142 Layer::PropertyType
Chris@0 143 TimeValueLayer::getPropertyType(const PropertyName &name) const
Chris@0 144 {
Chris@287 145 if (name == "Plot Type") return ValueProperty;
Chris@287 146 if (name == "Vertical Scale") return ValueProperty;
Chris@100 147 if (name == "Scale Units") return UnitsProperty;
Chris@1198 148 if (name == "Colour" && m_plotStyle == PlotSegmentation) return ColourMapProperty;
Chris@513 149 if (name == "Draw Segment Division Lines") return ToggleProperty;
Chris@553 150 if (name == "Show Derivative") return ToggleProperty;
Chris@287 151 return SingleColourLayer::getPropertyType(name);
Chris@0 152 }
Chris@0 153
Chris@198 154 QString
Chris@198 155 TimeValueLayer::getPropertyGroupName(const PropertyName &name) const
Chris@198 156 {
Chris@198 157 if (name == "Vertical Scale" || name == "Scale Units") {
Chris@198 158 return tr("Scale");
Chris@198 159 }
Chris@553 160 if (name == "Plot Type" || name == "Draw Segment Division Lines" ||
Chris@553 161 name == "Show Derivative") {
Chris@513 162 return tr("Plot Type");
Chris@513 163 }
Chris@287 164 return SingleColourLayer::getPropertyGroupName(name);
Chris@198 165 }
Chris@198 166
Chris@1470 167 bool
Chris@1470 168 TimeValueLayer::needsTextLabelHeight() const
Chris@1470 169 {
Chris@1474 170 auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
Chris@1470 171 if (!model) return false;
Chris@1470 172 return m_plotStyle == PlotSegmentation && model->hasTextLabels();
Chris@1470 173 }
Chris@1470 174
Chris@698 175 QString
Chris@698 176 TimeValueLayer::getScaleUnits() const
Chris@698 177 {
Chris@1474 178 auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
Chris@1474 179 if (model) return model->getScaleUnits();
Chris@698 180 else return "";
Chris@698 181 }
Chris@698 182
Chris@0 183 int
Chris@0 184 TimeValueLayer::getPropertyRangeAndValue(const PropertyName &name,
Chris@1266 185 int *min, int *max, int *deflt) const
Chris@0 186 {
Chris@216 187 int val = 0;
Chris@0 188
Chris@287 189 if (name == "Colour" && m_plotStyle == PlotSegmentation) {
Chris@197 190
Chris@287 191 if (min) *min = 0;
Chris@287 192 if (max) *max = ColourMapper::getColourMapCount() - 1;
Chris@287 193 if (deflt) *deflt = 0;
Chris@197 194
Chris@287 195 val = m_colourMap;
Chris@0 196
Chris@87 197 } else if (name == "Plot Type") {
Chris@1266 198
Chris@1266 199 if (min) *min = 0;
Chris@1266 200 if (max) *max = 6;
Chris@216 201 if (deflt) *deflt = int(PlotConnectedPoints);
Chris@1266 202
Chris@1266 203 val = int(m_plotStyle);
Chris@0 204
Chris@87 205 } else if (name == "Vertical Scale") {
Chris@1266 206
Chris@1266 207 if (min) *min = 0;
Chris@1266 208 if (max) *max = 3;
Chris@216 209 if (deflt) *deflt = int(AutoAlignScale);
Chris@1266 210
Chris@1266 211 val = int(m_verticalScale);
Chris@66 212
Chris@100 213 } else if (name == "Scale Units") {
Chris@100 214
Chris@216 215 if (deflt) *deflt = 0;
Chris@1474 216 auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
Chris@1474 217 if (model) {
Chris@216 218 val = UnitDatabase::getInstance()->getUnitId
Chris@698 219 (getScaleUnits());
Chris@100 220 }
Chris@100 221
Chris@513 222 } else if (name == "Draw Segment Division Lines") {
Chris@513 223
Chris@513 224 if (min) *min = 0;
Chris@1365 225 if (max) *max = 1;
Chris@513 226 if (deflt) *deflt = 1;
Chris@513 227 val = (m_drawSegmentDivisions ? 1.0 : 0.0);
Chris@513 228
Chris@553 229 } else if (name == "Show Derivative") {
Chris@553 230
Chris@553 231 if (min) *min = 0;
Chris@1365 232 if (max) *max = 1;
Chris@553 233 if (deflt) *deflt = 0;
Chris@553 234 val = (m_derivative ? 1.0 : 0.0);
Chris@553 235
Chris@0 236 } else {
Chris@1266 237
Chris@1266 238 val = SingleColourLayer::getPropertyRangeAndValue(name, min, max, deflt);
Chris@0 239 }
Chris@0 240
Chris@216 241 return val;
Chris@0 242 }
Chris@0 243
Chris@0 244 QString
Chris@0 245 TimeValueLayer::getPropertyValueLabel(const PropertyName &name,
Chris@1266 246 int value) const
Chris@0 247 {
Chris@287 248 if (name == "Colour" && m_plotStyle == PlotSegmentation) {
Chris@1362 249 return ColourMapper::getColourMapLabel(value);
Chris@87 250 } else if (name == "Plot Type") {
Chris@1266 251 switch (value) {
Chris@1266 252 default:
Chris@1266 253 case 0: return tr("Points");
Chris@1266 254 case 1: return tr("Stems");
Chris@1266 255 case 2: return tr("Connected Points");
Chris@1266 256 case 3: return tr("Lines");
Chris@1266 257 case 4: return tr("Curve");
Chris@1266 258 case 5: return tr("Segmentation");
Chris@1266 259 case 6: return tr("Discrete Curves");
Chris@1266 260 }
Chris@87 261 } else if (name == "Vertical Scale") {
Chris@1266 262 switch (value) {
Chris@1266 263 default:
Chris@1266 264 case 0: return tr("Auto-Align");
Chris@1266 265 case 1: return tr("Linear");
Chris@1266 266 case 2: return tr("Log");
Chris@1266 267 case 3: return tr("+/-1");
Chris@1266 268 }
Chris@0 269 }
Chris@287 270 return SingleColourLayer::getPropertyValueLabel(name, value);
Chris@0 271 }
Chris@0 272
Chris@0 273 void
Chris@0 274 TimeValueLayer::setProperty(const PropertyName &name, int value)
Chris@0 275 {
Chris@287 276 if (name == "Colour" && m_plotStyle == PlotSegmentation) {
Chris@287 277 setFillColourMap(value);
Chris@87 278 } else if (name == "Plot Type") {
Chris@1266 279 setPlotStyle(PlotStyle(value));
Chris@87 280 } else if (name == "Vertical Scale") {
Chris@1266 281 setVerticalScale(VerticalScale(value));
Chris@100 282 } else if (name == "Scale Units") {
Chris@1474 283 auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
Chris@1474 284 if (model) {
Chris@1474 285 model->setScaleUnits
Chris@100 286 (UnitDatabase::getInstance()->getUnitById(value));
Chris@1481 287 emit modelChanged(m_model);
Chris@100 288 }
Chris@513 289 } else if (name == "Draw Segment Division Lines") {
Chris@513 290 setDrawSegmentDivisions(value > 0.5);
Chris@553 291 } else if (name == "Show Derivative") {
Chris@553 292 setShowDerivative(value > 0.5);
Chris@287 293 } else {
Chris@287 294 SingleColourLayer::setProperty(name, value);
Chris@0 295 }
Chris@0 296 }
Chris@0 297
Chris@0 298 void
Chris@197 299 TimeValueLayer::setFillColourMap(int map)
Chris@197 300 {
Chris@197 301 if (m_colourMap == map) return;
Chris@197 302 m_colourMap = map;
Chris@197 303 emit layerParametersChanged();
Chris@197 304 }
Chris@197 305
Chris@197 306 void
Chris@0 307 TimeValueLayer::setPlotStyle(PlotStyle style)
Chris@0 308 {
Chris@0 309 if (m_plotStyle == style) return;
Chris@197 310 bool colourTypeChanged = (style == PlotSegmentation ||
Chris@197 311 m_plotStyle == PlotSegmentation);
Chris@0 312 m_plotStyle = style;
Chris@197 313 if (colourTypeChanged) {
Chris@197 314 emit layerParameterRangesChanged();
Chris@197 315 }
Chris@0 316 emit layerParametersChanged();
Chris@0 317 }
Chris@0 318
Chris@66 319 void
Chris@66 320 TimeValueLayer::setVerticalScale(VerticalScale scale)
Chris@66 321 {
Chris@66 322 if (m_verticalScale == scale) return;
Chris@66 323 m_verticalScale = scale;
Chris@66 324 emit layerParametersChanged();
Chris@66 325 }
Chris@66 326
Chris@513 327 void
Chris@513 328 TimeValueLayer::setDrawSegmentDivisions(bool draw)
Chris@513 329 {
Chris@513 330 if (m_drawSegmentDivisions == draw) return;
Chris@513 331 m_drawSegmentDivisions = draw;
Chris@513 332 emit layerParametersChanged();
Chris@513 333 }
Chris@513 334
Chris@553 335 void
Chris@553 336 TimeValueLayer::setShowDerivative(bool show)
Chris@553 337 {
Chris@553 338 if (m_derivative == show) return;
Chris@553 339 m_derivative = show;
Chris@553 340 emit layerParametersChanged();
Chris@553 341 }
Chris@553 342
Chris@0 343 bool
Chris@918 344 TimeValueLayer::isLayerScrollable(const LayerGeometryProvider *v) const
Chris@0 345 {
Chris@6 346 // We don't illuminate sections in the line or curve modes, so
Chris@6 347 // they're always scrollable
Chris@6 348
Chris@6 349 if (m_plotStyle == PlotLines ||
Chris@1266 350 m_plotStyle == PlotCurve ||
Chris@615 351 m_plotStyle == PlotDiscreteCurves) return true;
Chris@6 352
Chris@0 353 QPoint discard;
Chris@44 354 return !v->shouldIlluminateLocalFeatures(this, discard);
Chris@0 355 }
Chris@0 356
Chris@79 357 bool
Chris@908 358 TimeValueLayer::getValueExtents(double &min, double &max,
Chris@101 359 bool &logarithmic, QString &unit) const
Chris@79 360 {
Chris@1474 361 auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
Chris@1474 362 if (!model) return false;
Chris@668 363
Chris@1474 364 min = model->getValueMinimum();
Chris@1474 365 max = model->getValueMaximum();
Chris@668 366
Chris@101 367 logarithmic = (m_verticalScale == LogScale);
Chris@668 368
Chris@698 369 unit = getScaleUnits();
Chris@668 370
Chris@553 371 if (m_derivative) {
Chris@908 372 max = std::max(fabs(min), fabs(max));
Chris@553 373 min = -max;
Chris@553 374 }
Chris@629 375
Chris@629 376 #ifdef DEBUG_TIME_VALUE_LAYER
Chris@682 377 cerr << "TimeValueLayer::getValueExtents: min = " << min << ", max = " << max << endl;
Chris@629 378 #endif
Chris@629 379
Chris@668 380 if (!shouldAutoAlign() && !logarithmic && !m_derivative) {
Chris@668 381
Chris@668 382 if (max == min) {
Chris@668 383 max = max + 0.5;
Chris@668 384 min = min - 0.5;
Chris@668 385 } else {
Chris@908 386 double margin = (max - min) / 10.0;
Chris@668 387 max = max + margin;
Chris@668 388 min = min - margin;
Chris@668 389 }
Chris@668 390
Chris@668 391 #ifdef DEBUG_TIME_VALUE_LAYER
Chris@682 392 cerr << "TimeValueLayer::getValueExtents: min = " << min << ", max = " << max << " (after adjustment)" << endl;
Chris@668 393 #endif
Chris@668 394 }
Chris@668 395
Chris@79 396 return true;
Chris@79 397 }
Chris@79 398
Chris@101 399 bool
Chris@908 400 TimeValueLayer::getDisplayExtents(double &min, double &max) const
Chris@101 401 {
Chris@1474 402 auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
Chris@1474 403 if (!model || shouldAutoAlign()) return false;
Chris@101 404
Chris@437 405 if (m_scaleMinimum == m_scaleMaximum) {
Chris@668 406 bool log;
Chris@668 407 QString unit;
Chris@668 408 getValueExtents(min, max, log, unit);
Chris@553 409 } else {
Chris@553 410 min = m_scaleMinimum;
Chris@553 411 max = m_scaleMaximum;
Chris@437 412 }
Chris@437 413
Chris@553 414 if (m_derivative) {
Chris@908 415 max = std::max(fabs(min), fabs(max));
Chris@553 416 min = -max;
Chris@553 417 }
Chris@437 418
Chris@526 419 #ifdef DEBUG_TIME_VALUE_LAYER
Chris@682 420 cerr << "TimeValueLayer::getDisplayExtents: min = " << min << ", max = " << max << endl;
Chris@526 421 #endif
Chris@437 422
Chris@101 423 return true;
Chris@101 424 }
Chris@101 425
Chris@437 426 bool
Chris@908 427 TimeValueLayer::setDisplayExtents(double min, double max)
Chris@437 428 {
Chris@1474 429 auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
Chris@1474 430 if (!model) return false;
Chris@437 431
Chris@437 432 if (min == max) {
Chris@437 433 if (min == 0.f) {
Chris@437 434 max = 1.f;
Chris@437 435 } else {
Chris@437 436 max = min * 1.0001;
Chris@437 437 }
Chris@437 438 }
Chris@437 439
Chris@437 440 m_scaleMinimum = min;
Chris@437 441 m_scaleMaximum = max;
Chris@437 442
Chris@526 443 #ifdef DEBUG_TIME_VALUE_LAYER
Chris@682 444 cerr << "TimeValueLayer::setDisplayExtents: min = " << min << ", max = " << max << endl;
Chris@526 445 #endif
Chris@437 446
Chris@437 447 emit layerParametersChanged();
Chris@437 448 return true;
Chris@437 449 }
Chris@437 450
Chris@437 451 int
Chris@437 452 TimeValueLayer::getVerticalZoomSteps(int &defaultStep) const
Chris@437 453 {
Chris@437 454 if (shouldAutoAlign()) return 0;
Chris@1474 455 auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
Chris@1474 456 if (!model) return 0;
Chris@437 457
Chris@439 458 defaultStep = 0;
Chris@437 459 return 100;
Chris@437 460 }
Chris@437 461
Chris@437 462 int
Chris@437 463 TimeValueLayer::getCurrentVerticalZoomStep() const
Chris@437 464 {
Chris@437 465 if (shouldAutoAlign()) return 0;
Chris@1474 466 auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
Chris@1474 467 if (!model) return 0;
Chris@437 468
Chris@437 469 RangeMapper *mapper = getNewVerticalZoomRangeMapper();
Chris@437 470 if (!mapper) return 0;
Chris@437 471
Chris@908 472 double dmin, dmax;
Chris@437 473 getDisplayExtents(dmin, dmax);
Chris@437 474
Chris@437 475 int nr = mapper->getPositionForValue(dmax - dmin);
Chris@526 476
Chris@526 477 #ifdef DEBUG_TIME_VALUE_LAYER
Chris@682 478 cerr << "TimeValueLayer::getCurrentVerticalZoomStep: dmin = " << dmin << ", dmax = " << dmax << ", nr = " << nr << endl;
Chris@526 479 #endif
Chris@526 480
Chris@437 481 delete mapper;
Chris@437 482
Chris@437 483 return 100 - nr;
Chris@437 484 }
Chris@437 485
Chris@437 486 void
Chris@437 487 TimeValueLayer::setVerticalZoomStep(int step)
Chris@437 488 {
Chris@437 489 if (shouldAutoAlign()) return;
Chris@1474 490 auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
Chris@1474 491 if (!model) return;
Chris@437 492
Chris@437 493 RangeMapper *mapper = getNewVerticalZoomRangeMapper();
Chris@437 494 if (!mapper) return;
Chris@437 495
Chris@908 496 double min, max;
Chris@437 497 bool logarithmic;
Chris@437 498 QString unit;
Chris@437 499 getValueExtents(min, max, logarithmic, unit);
Chris@437 500
Chris@908 501 double dmin, dmax;
Chris@437 502 getDisplayExtents(dmin, dmax);
Chris@437 503
Chris@908 504 double newdist = mapper->getValueForPosition(100 - step);
Chris@437 505
Chris@908 506 double newmin, newmax;
Chris@437 507
Chris@437 508 if (logarithmic) {
Chris@437 509
Chris@437 510 // see SpectrogramLayer::setVerticalZoomStep
Chris@437 511
Chris@908 512 newmax = (newdist + sqrt(newdist*newdist + 4*dmin*dmax)) / 2;
Chris@437 513 newmin = newmax - newdist;
Chris@437 514
Chris@526 515 #ifdef DEBUG_TIME_VALUE_LAYER
Chris@682 516 cerr << "newmin = " << newmin << ", newmax = " << newmax << endl;
Chris@526 517 #endif
Chris@437 518
Chris@437 519 } else {
Chris@908 520 double dmid = (dmax + dmin) / 2;
Chris@437 521 newmin = dmid - newdist / 2;
Chris@437 522 newmax = dmid + newdist / 2;
Chris@437 523 }
Chris@437 524
Chris@437 525 if (newmin < min) {
Chris@437 526 newmax += (min - newmin);
Chris@437 527 newmin = min;
Chris@437 528 }
Chris@437 529 if (newmax > max) {
Chris@437 530 newmax = max;
Chris@437 531 }
Chris@437 532
Chris@526 533 #ifdef DEBUG_TIME_VALUE_LAYER
Chris@682 534 cerr << "TimeValueLayer::setVerticalZoomStep: " << step << ": " << newmin << " -> " << newmax << " (range " << newdist << ")" << endl;
Chris@526 535 #endif
Chris@437 536
Chris@437 537 setDisplayExtents(newmin, newmax);
Chris@437 538 }
Chris@437 539
Chris@437 540 RangeMapper *
Chris@437 541 TimeValueLayer::getNewVerticalZoomRangeMapper() const
Chris@437 542 {
Chris@1474 543 auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
Chris@1474 544 if (!model) return nullptr;
Chris@437 545
Chris@437 546 RangeMapper *mapper;
Chris@437 547
Chris@908 548 double min, max;
Chris@437 549 bool logarithmic;
Chris@437 550 QString unit;
Chris@437 551 getValueExtents(min, max, logarithmic, unit);
Chris@437 552
Chris@1408 553 if (min == max) return nullptr;
Chris@437 554
Chris@437 555 if (logarithmic) {
Chris@437 556 mapper = new LogRangeMapper(0, 100, min, max, unit);
Chris@437 557 } else {
Chris@437 558 mapper = new LinearRangeMapper(0, 100, min, max, unit);
Chris@437 559 }
Chris@437 560
Chris@437 561 return mapper;
Chris@437 562 }
Chris@437 563
Chris@1429 564 EventVector
Chris@918 565 TimeValueLayer::getLocalPoints(LayerGeometryProvider *v, int x) const
Chris@0 566 {
Chris@1474 567 auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
Chris@1474 568 if (!model) return {};
Chris@0 569
Chris@1431 570 // Return all points at a frame f, where f is the closest frame to
Chris@1431 571 // pixel coordinate x whose pixel coordinate is both within a
Chris@1431 572 // small (but somewhat arbitrary) fuzz distance from x and within
Chris@1431 573 // the current view. If there is no such frame, return an empty
Chris@1431 574 // vector.
Chris@1431 575
Chris@908 576 sv_frame_t frame = v->getFrameForX(x);
Chris@1431 577
Chris@1474 578 EventVector exact = model->getEventsStartingAt(frame);
Chris@1431 579 if (!exact.empty()) return exact;
Chris@0 580
Chris@1431 581 // overspill == 1, so one event either side of the given span
Chris@1474 582 EventVector neighbouring = model->getEventsWithin
Chris@1474 583 (frame, model->getResolution(), 1);
Chris@1431 584
Chris@1431 585 double fuzz = v->scaleSize(2);
Chris@1431 586 sv_frame_t suitable = 0;
Chris@1431 587 bool have = false;
Chris@1429 588
Chris@1431 589 for (Event e: neighbouring) {
Chris@1431 590 sv_frame_t f = e.getFrame();
Chris@1431 591 if (f < v->getStartFrame() || f > v->getEndFrame()) {
Chris@1431 592 continue;
Chris@1431 593 }
Chris@1431 594 int px = v->getXForFrame(f);
Chris@1431 595 if ((px > x && px - x > fuzz) || (px < x && x - px > fuzz + 3)) {
Chris@1431 596 continue;
Chris@1431 597 }
Chris@1431 598 if (!have) {
Chris@1431 599 suitable = f;
Chris@1431 600 have = true;
Chris@1431 601 } else if (llabs(frame - f) < llabs(suitable - f)) {
Chris@1431 602 suitable = f;
Chris@1266 603 }
Chris@28 604 }
Chris@28 605
Chris@1431 606 if (have) {
Chris@1474 607 return model->getEventsStartingAt(suitable);
Chris@1431 608 } else {
Chris@1431 609 return {};
Chris@1431 610 }
Chris@0 611 }
Chris@0 612
Chris@25 613 QString
Chris@1431 614 TimeValueLayer::getLabelPreceding(sv_frame_t frame) const
Chris@552 615 {
Chris@1474 616 auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
Chris@1474 617 if (!model || !model->hasTextLabels()) return "";
Chris@1431 618
Chris@1431 619 Event e;
Chris@1474 620 if (model->getNearestEventMatching
Chris@1431 621 (frame,
Chris@1431 622 [](Event e) { return e.hasLabel() && e.getLabel() != ""; },
Chris@1431 623 EventSeries::Backward,
Chris@1431 624 e)) {
Chris@1431 625 return e.getLabel();
Chris@552 626 }
Chris@1431 627
Chris@552 628 return "";
Chris@552 629 }
Chris@552 630
Chris@552 631 QString
Chris@918 632 TimeValueLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &pos) const
Chris@0 633 {
Chris@25 634 int x = pos.x();
Chris@0 635
Chris@1474 636 auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
Chris@1474 637 if (!model || !model->getSampleRate()) return "";
Chris@0 638
Chris@1429 639 EventVector points = getLocalPoints(v, x);
Chris@0 640
Chris@0 641 if (points.empty()) {
Chris@1474 642 if (!model->isReady()) {
Chris@1266 643 return tr("In progress");
Chris@1266 644 } else {
Chris@1266 645 return tr("No local points");
Chris@1266 646 }
Chris@0 647 }
Chris@0 648
Chris@1429 649 sv_frame_t useFrame = points.begin()->getFrame();
Chris@0 650
Chris@1474 651 RealTime rt = RealTime::frame2RealTime(useFrame, model->getSampleRate());
Chris@25 652
Chris@960 653 QString valueText;
Chris@1429 654 float value = points.begin()->getValue();
Chris@960 655 QString unit = getScaleUnits();
Chris@960 656
Chris@960 657 if (unit == "Hz") {
Chris@960 658 valueText = tr("%1 Hz (%2, %3)")
Chris@960 659 .arg(value)
Chris@960 660 .arg(Pitch::getPitchLabelForFrequency(value))
Chris@960 661 .arg(Pitch::getPitchForFrequency(value));
Chris@960 662 } else if (unit != "") {
Chris@960 663 valueText = tr("%1 %2").arg(value).arg(unit);
Chris@960 664 } else {
Chris@960 665 valueText = tr("%1").arg(value);
Chris@960 666 }
Chris@960 667
Chris@25 668 QString text;
Chris@0 669
Chris@1429 670 if (points.begin()->getLabel() == "") {
Chris@1266 671 text = QString(tr("Time:\t%1\nValue:\t%2\nNo label"))
Chris@1266 672 .arg(rt.toText(true).c_str())
Chris@1266 673 .arg(valueText);
Chris@101 674 } else {
Chris@1266 675 text = QString(tr("Time:\t%1\nValue:\t%2\nLabel:\t%4"))
Chris@1266 676 .arg(rt.toText(true).c_str())
Chris@1266 677 .arg(valueText)
Chris@1429 678 .arg(points.begin()->getLabel());
Chris@25 679 }
Chris@0 680
Chris@44 681 pos = QPoint(v->getXForFrame(useFrame),
Chris@1429 682 getYForValue(v, points.begin()->getValue()));
Chris@25 683 return text;
Chris@0 684 }
Chris@0 685
Chris@28 686 bool
Chris@1429 687 TimeValueLayer::snapToFeatureFrame(LayerGeometryProvider *v,
Chris@1429 688 sv_frame_t &frame,
Chris@1266 689 int &resolution,
Chris@1266 690 SnapType snap) const
Chris@13 691 {
Chris@1474 692 auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
Chris@1474 693 if (!model) {
Chris@1266 694 return Layer::snapToFeatureFrame(v, frame, resolution, snap);
Chris@13 695 }
Chris@13 696
Chris@1431 697 // SnapLeft / SnapRight: return frame of nearest feature in that
Chris@1431 698 // direction no matter how far away
Chris@1431 699 //
Chris@1431 700 // SnapNeighbouring: return frame of feature that would be used in
Chris@1431 701 // an editing operation, i.e. closest feature in either direction
Chris@1431 702 // but only if it is "close enough"
Chris@1431 703
Chris@1474 704 resolution = model->getResolution();
Chris@13 705
Chris@28 706 if (snap == SnapNeighbouring) {
Chris@1431 707 EventVector points = getLocalPoints(v, v->getXForFrame(frame));
Chris@1266 708 if (points.empty()) return false;
Chris@1429 709 frame = points.begin()->getFrame();
Chris@1266 710 return true;
Chris@13 711 }
Chris@13 712
Chris@1431 713 Event e;
Chris@1474 714 if (model->getNearestEventMatching
Chris@1431 715 (frame,
Chris@1431 716 [](Event) { return true; },
Chris@1431 717 snap == SnapLeft ? EventSeries::Backward : EventSeries::Forward,
Chris@1431 718 e)) {
Chris@1431 719 frame = e.getFrame();
Chris@1431 720 return true;
Chris@1431 721 }
Chris@1431 722
Chris@1431 723 return false;
Chris@13 724 }
Chris@13 725
Chris@517 726 bool
Chris@1431 727 TimeValueLayer::snapToSimilarFeature(LayerGeometryProvider *v,
Chris@1431 728 sv_frame_t &frame,
Chris@805 729 int &resolution,
Chris@517 730 SnapType snap) const
Chris@517 731 {
Chris@1474 732 auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
Chris@1474 733 if (!model) {
Chris@1266 734 return Layer::snapToSimilarFeature(v, frame, resolution, snap);
Chris@517 735 }
Chris@517 736
Chris@1432 737 // snap is only permitted to be SnapLeft or SnapRight here.
Chris@1432 738
Chris@1474 739 resolution = model->getResolution();
Chris@517 740
Chris@1431 741 Event ref;
Chris@1431 742 Event e;
Chris@1431 743 float matchvalue;
Chris@1431 744 bool found;
Chris@517 745
Chris@1474 746 found = model->getNearestEventMatching
Chris@1431 747 (frame, [](Event) { return true; }, EventSeries::Backward, ref);
Chris@1429 748
Chris@1431 749 if (!found) {
Chris@1431 750 return false;
Chris@517 751 }
Chris@517 752
Chris@1431 753 matchvalue = ref.getValue();
Chris@1431 754
Chris@1474 755 found = model->getNearestEventMatching
Chris@1431 756 (frame,
Chris@1431 757 [matchvalue](Event e) {
Chris@1431 758 double epsilon = 0.0001;
Chris@1431 759 return fabs(e.getValue() - matchvalue) < epsilon;
Chris@1431 760 },
Chris@1431 761 snap == SnapLeft ? EventSeries::Backward : EventSeries::Forward,
Chris@1431 762 e);
Chris@517 763
Chris@1431 764 if (!found) {
Chris@1431 765 return false;
Chris@517 766 }
Chris@517 767
Chris@1431 768 frame = e.getFrame();
Chris@1431 769 return true;
Chris@517 770 }
Chris@517 771
Chris@101 772 void
Chris@918 773 TimeValueLayer::getScaleExtents(LayerGeometryProvider *v, double &min, double &max, bool &log) const
Chris@101 774 {
Chris@101 775 min = 0.0;
Chris@101 776 max = 0.0;
Chris@101 777 log = false;
Chris@101 778
Chris@1474 779 auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
Chris@1474 780 if (!model) return;
Chris@1474 781
Chris@296 782 if (shouldAutoAlign()) {
Chris@101 783
Chris@698 784 if (!v->getValueExtents(getScaleUnits(), min, max, log)) {
Chris@1474 785 min = model->getValueMinimum();
Chris@1474 786 max = model->getValueMaximum();
Chris@668 787 } else if (log) {
Chris@668 788 LogRange::mapRange(min, max);
Chris@101 789 }
Chris@101 790
Chris@101 791 } else if (m_verticalScale == PlusMinusOneScale) {
Chris@101 792
Chris@101 793 min = -1.0;
Chris@101 794 max = 1.0;
Chris@101 795
Chris@101 796 } else {
Chris@101 797
Chris@437 798 getDisplayExtents(min, max);
Chris@632 799
Chris@101 800 if (m_verticalScale == LogScale) {
Chris@197 801 LogRange::mapRange(min, max);
Chris@101 802 log = true;
Chris@101 803 }
Chris@101 804 }
Chris@101 805
Chris@629 806 #ifdef DEBUG_TIME_VALUE_LAYER
Chris@682 807 cerr << "TimeValueLayer::getScaleExtents: min = " << min << ", max = " << max << endl;
Chris@629 808 #endif
Chris@101 809 }
Chris@101 810
Chris@21 811 int
Chris@918 812 TimeValueLayer::getYForValue(LayerGeometryProvider *v, double val) const
Chris@21 813 {
Chris@908 814 double min = 0.0, max = 0.0;
Chris@101 815 bool logarithmic = false;
Chris@918 816 int h = v->getPaintHeight();
Chris@79 817
Chris@101 818 getScaleExtents(v, min, max, logarithmic);
Chris@101 819
Chris@526 820 #ifdef DEBUG_TIME_VALUE_LAYER
Chris@682 821 cerr << "getYForValue(" << val << "): min " << min << ", max "
Chris@682 822 << max << ", log " << logarithmic << endl;
Chris@526 823 #endif
Chris@101 824
Chris@101 825 if (logarithmic) {
Chris@197 826 val = LogRange::map(val);
Chris@79 827 }
Chris@79 828
Chris@66 829 return int(h - ((val - min) * h) / (max - min));
Chris@21 830 }
Chris@21 831
Chris@908 832 double
Chris@918 833 TimeValueLayer::getValueForY(LayerGeometryProvider *v, int y) const
Chris@21 834 {
Chris@908 835 double min = 0.0, max = 0.0;
Chris@101 836 bool logarithmic = false;
Chris@918 837 int h = v->getPaintHeight();
Chris@21 838
Chris@101 839 getScaleExtents(v, min, max, logarithmic);
Chris@101 840
Chris@908 841 double val = min + (double(h - y) * double(max - min)) / h;
Chris@101 842
Chris@101 843 if (logarithmic) {
Chris@437 844 val = LogRange::map(val);
Chris@101 845 }
Chris@101 846
Chris@101 847 return val;
Chris@21 848 }
Chris@21 849
Chris@296 850 bool
Chris@296 851 TimeValueLayer::shouldAutoAlign() const
Chris@296 852 {
Chris@698 853 QString unit = getScaleUnits();
Chris@296 854 return (m_verticalScale == AutoAlignScale && unit != "");
Chris@296 855 }
Chris@296 856
Chris@68 857 QColor
Chris@918 858 TimeValueLayer::getColourForValue(LayerGeometryProvider *v, double val) const
Chris@68 859 {
Chris@908 860 double min, max;
Chris@101 861 bool log;
Chris@101 862 getScaleExtents(v, min, max, log);
Chris@68 863
Chris@197 864 if (min > max) std::swap(min, max);
Chris@197 865 if (max == min) max = min + 1;
Chris@197 866
Chris@101 867 if (log) {
Chris@197 868 val = LogRange::map(val);
Chris@68 869 }
Chris@68 870
Chris@526 871 #ifdef DEBUG_TIME_VALUE_LAYER
Chris@682 872 cerr << "TimeValueLayer::getColourForValue: min " << min << ", max "
Chris@682 873 << max << ", log " << log << ", value " << val << endl;
Chris@526 874 #endif
Chris@68 875
Chris@1362 876 QColor solid = ColourMapper(m_colourMap, m_colourInverted, min, max).map(val);
Chris@197 877 return QColor(solid.red(), solid.green(), solid.blue(), 120);
Chris@68 878 }
Chris@68 879
Chris@287 880 int
Chris@287 881 TimeValueLayer::getDefaultColourHint(bool darkbg, bool &impose)
Chris@287 882 {
Chris@287 883 impose = false;
Chris@287 884 return ColourDatabase::getInstance()->getColourIndex
Chris@287 885 (QString(darkbg ? "Bright Green" : "Green"));
Chris@287 886 }
Chris@287 887
Chris@0 888 void
Chris@916 889 TimeValueLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
Chris@0 890 {
Chris@1474 891 auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
Chris@1474 892 if (!model || !model->isOK()) return;
Chris@0 893
Chris@1474 894 sv_samplerate_t sampleRate = model->getSampleRate();
Chris@0 895 if (!sampleRate) return;
Chris@0 896
Chris@1492 897 #ifdef DEBUG_TIME_VALUE_LAYER
Chris@1492 898 SVCERR << "TimeValueLayer[" << this << ", model " << getModel() << "]::paint in " << v->getId() << endl;
Chris@1492 899 #endif
Chris@1492 900
Chris@353 901 paint.setRenderHint(QPainter::Antialiasing, false);
Chris@353 902
Chris@0 903 // Profiler profiler("TimeValueLayer::paint", true);
Chris@0 904
Chris@0 905 int x0 = rect.left(), x1 = rect.right();
Chris@908 906 sv_frame_t frame0 = v->getFrameForX(x0);
Chris@908 907 sv_frame_t frame1 = v->getFrameForX(x1);
Chris@553 908 if (m_derivative) --frame0;
Chris@0 909
Chris@1474 910 EventVector points(model->getEventsWithin(frame0, frame1 - frame0, 1));
Chris@1492 911
Chris@1492 912 #ifdef DEBUG_TIME_VALUE_LAYER
Chris@1492 913 SVCERR << "TimeValueLayer[" << this << "]::paint in " << v->getId()
Chris@1492 914 << ": pixel extents " << x0 << " to " << x1 << ", frame extents "
Chris@1492 915 << frame0 << " to " << frame1 << " yielding " << points.size()
Chris@1492 916 << " points (of " << model->getAllEvents().size()
Chris@1492 917 << " from frames " << model->getStartFrame() << " to "
Chris@1492 918 << model->getEndFrame() << ")" << endl;
Chris@1492 919 #endif
Chris@1492 920
Chris@11 921 if (points.empty()) return;
Chris@0 922
Chris@287 923 paint.setPen(getBaseQColor());
Chris@0 924
Chris@287 925 QColor brushColour(getBaseQColor());
Chris@0 926 brushColour.setAlpha(80);
Chris@0 927 paint.setBrush(brushColour);
Chris@0 928
Chris@526 929 #ifdef DEBUG_TIME_VALUE_LAYER
Chris@682 930 cerr << "TimeValueLayer::paint: resolution is "
Chris@1474 931 << model->getResolution() << " frames" << endl;
Chris@526 932 #endif
Chris@0 933
Chris@1474 934 double min = model->getValueMinimum();
Chris@1474 935 double max = model->getValueMaximum();
Chris@0 936 if (max == min) max = min + 1.0;
Chris@0 937
Chris@918 938 int origin = int(nearbyint(v->getPaintHeight() -
Chris@1266 939 (-min * v->getPaintHeight()) / (max - min)));
Chris@0 940
Chris@0 941 QPoint localPos;
Chris@908 942 sv_frame_t illuminateFrame = -1;
Chris@0 943
Chris@44 944 if (v->shouldIlluminateLocalFeatures(this, localPos)) {
Chris@1429 945 EventVector localPoints = getLocalPoints(v, localPos.x());
Chris@526 946 #ifdef DEBUG_TIME_VALUE_LAYER
Chris@682 947 cerr << "TimeValueLayer: " << localPoints.size() << " local points" << endl;
Chris@526 948 #endif
Chris@1429 949 if (!localPoints.empty()) {
Chris@1429 950 illuminateFrame = localPoints.begin()->getFrame();
Chris@1429 951 }
Chris@0 952 }
Chris@6 953
Chris@20 954 int w =
Chris@1474 955 v->getXForFrame(frame0 + model->getResolution()) -
Chris@1266 956 v->getXForFrame(frame0);
Chris@7 957
Chris@629 958 if (m_plotStyle == PlotStems) {
Chris@629 959 if (w < 2) w = 2;
Chris@629 960 } else {
Chris@629 961 if (w < 1) w = 1;
Chris@629 962 }
Chris@629 963
Chris@6 964 paint.save();
Chris@6 965
Chris@6 966 QPainterPath path;
Chris@55 967 int pointCount = 0;
Chris@79 968
Chris@79 969 int textY = 0;
Chris@79 970 if (m_plotStyle == PlotSegmentation) {
Chris@79 971 textY = v->getTextLabelHeight(this, paint);
Chris@554 972 } else {
Chris@554 973 int originY = getYForValue(v, 0.f);
Chris@918 974 if (originY > 0 && originY < v->getPaintHeight()) {
Chris@554 975 paint.save();
Chris@554 976 paint.setPen(getPartialShades(v)[1]);
Chris@554 977 paint.drawLine(x0, originY, x1, originY);
Chris@554 978 paint.restore();
Chris@554 979 }
Chris@79 980 }
Chris@6 981
Chris@908 982 sv_frame_t prevFrame = 0;
Chris@615 983
Chris@1429 984 for (EventVector::const_iterator i = points.begin();
Chris@1266 985 i != points.end(); ++i) {
Chris@0 986
Chris@553 987 if (m_derivative && i == points.begin()) continue;
Chris@721 988
Chris@1429 989 Event p(*i);
Chris@0 990
Chris@1429 991 double value = p.getValue();
Chris@553 992 if (m_derivative) {
Chris@1429 993 EventVector::const_iterator j = i;
Chris@553 994 --j;
Chris@1429 995 value -= j->getValue();
Chris@553 996 }
Chris@553 997
Chris@1429 998 int x = v->getXForFrame(p.getFrame());
Chris@1266 999 int y = getYForValue(v, value);
Chris@0 1000
Chris@615 1001 bool gap = false;
Chris@615 1002 if (m_plotStyle == PlotDiscreteCurves) {
Chris@721 1003 if (value == 0.0) {
Chris@721 1004 // Treat zeros as gaps
Chris@721 1005 continue;
Chris@721 1006 }
Chris@1429 1007 gap = (p.getFrame() > prevFrame &&
Chris@1474 1008 (p.getFrame() - prevFrame >= model->getResolution() * 2));
Chris@615 1009 }
Chris@615 1010
Chris@79 1011 if (m_plotStyle != PlotSegmentation) {
Chris@79 1012 textY = y - paint.fontMetrics().height()
Chris@631 1013 + paint.fontMetrics().ascent() - 1;
Chris@372 1014 if (textY < paint.fontMetrics().ascent() + 1) {
Chris@372 1015 textY = paint.fontMetrics().ascent() + 1;
Chris@372 1016 }
Chris@79 1017 }
Chris@79 1018
Chris@1266 1019 bool haveNext = false;
Chris@908 1020 double nvalue = 0.f;
Chris@908 1021 sv_frame_t nf = v->getModelsEndFrame();
Chris@1266 1022 int nx = v->getXForFrame(nf);
Chris@1266 1023 int ny = y;
Chris@34 1024
Chris@1429 1025 EventVector::const_iterator j = i;
Chris@1266 1026 ++j;
Chris@34 1027
Chris@1266 1028 if (j != points.end()) {
Chris@1429 1029 Event q(*j);
Chris@1429 1030 nvalue = q.getValue();
Chris@1429 1031 if (m_derivative) nvalue -= p.getValue();
Chris@1429 1032 nf = q.getFrame();
Chris@1266 1033 nx = v->getXForFrame(nf);
Chris@1266 1034 ny = getYForValue(v, nvalue);
Chris@1266 1035 haveNext = true;
Chris@76 1036 }
Chris@76 1037
Chris@1429 1038 // cout << "frame = " << p.getFrame() << ", x = " << x << ", haveNext = " << haveNext
Chris@682 1039 // << ", nx = " << nx << endl;
Chris@34 1040
Chris@1229 1041 QPen pen(getBaseQColor());
Chris@1229 1042 QBrush brush(brushColour);
Chris@1229 1043
Chris@615 1044 if (m_plotStyle == PlotDiscreteCurves) {
Chris@1229 1045 pen = QPen(getBaseQColor(), 3);
Chris@1229 1046 brush = QBrush(Qt::NoBrush);
Chris@615 1047 } else if (m_plotStyle == PlotSegmentation) {
Chris@1229 1048 pen = QPen(getForegroundQColor(v));
Chris@1229 1049 brush = QBrush(getColourForValue(v, value));
Chris@1266 1050 } else if (m_plotStyle == PlotLines ||
Chris@1266 1051 m_plotStyle == PlotCurve) {
Chris@1229 1052 brush = QBrush(Qt::NoBrush);
Chris@1266 1053 }
Chris@1229 1054
Chris@1401 1055 paint.setPen(v->scalePen(pen));
Chris@1229 1056 paint.setBrush(brush);
Chris@1229 1057
Chris@1266 1058 if (m_plotStyle == PlotStems) {
Chris@1266 1059 if (y < origin - 1) {
Chris@1266 1060 paint.drawLine(x + w/2, y + 1, x + w/2, origin);
Chris@1266 1061 } else if (y > origin + 1) {
Chris@1266 1062 paint.drawLine(x + w/2, origin, x + w/2, y - 1);
Chris@1266 1063 }
Chris@1266 1064 }
Chris@0 1065
Chris@631 1066 bool illuminate = false;
Chris@631 1067
Chris@1429 1068 if (illuminateFrame == p.getFrame()) {
Chris@6 1069
Chris@1266 1070 // not equipped to illuminate the right section in line
Chris@1266 1071 // or curve mode
Chris@6 1072
Chris@1266 1073 if (m_plotStyle != PlotCurve &&
Chris@615 1074 m_plotStyle != PlotDiscreteCurves &&
Chris@1266 1075 m_plotStyle != PlotLines) {
Chris@631 1076 illuminate = true;
Chris@631 1077 }
Chris@631 1078 }
Chris@0 1079
Chris@1266 1080 if (m_plotStyle != PlotLines &&
Chris@1266 1081 m_plotStyle != PlotCurve &&
Chris@615 1082 m_plotStyle != PlotDiscreteCurves &&
Chris@1266 1083 m_plotStyle != PlotSegmentation) {
Chris@631 1084 if (illuminate) {
Chris@631 1085 paint.save();
Chris@1401 1086 paint.setPen(v->scalePen(getForegroundQColor(v)));
Chris@631 1087 paint.setBrush(getForegroundQColor(v));
Chris@631 1088 }
Chris@326 1089 if (m_plotStyle != PlotStems ||
Chris@326 1090 w > 1) {
Chris@326 1091 paint.drawRect(x, y - 1, w, 2);
Chris@326 1092 }
Chris@631 1093 if (illuminate) {
Chris@631 1094 paint.restore();
Chris@631 1095 }
Chris@1266 1096 }
Chris@0 1097
Chris@1266 1098 if (m_plotStyle == PlotConnectedPoints ||
Chris@1266 1099 m_plotStyle == PlotLines ||
Chris@615 1100 m_plotStyle == PlotDiscreteCurves ||
Chris@1266 1101 m_plotStyle == PlotCurve) {
Chris@0 1102
Chris@1266 1103 if (haveNext) {
Chris@3 1104
Chris@1266 1105 if (m_plotStyle == PlotConnectedPoints) {
Chris@1266 1106
Chris@79 1107 paint.save();
Chris@1401 1108 paint.setPen(v->scalePen(brushColour));
Chris@1266 1109 paint.drawLine(x + w, y, nx, ny);
Chris@79 1110 paint.restore();
Chris@6 1111
Chris@1266 1112 } else if (m_plotStyle == PlotLines) {
Chris@430 1113
Chris@430 1114 if (pointCount == 0) {
Chris@430 1115 path.moveTo(x + w/2, y);
Chris@430 1116 }
Chris@6 1117
Chris@1266 1118 // paint.drawLine(x + w/2, y, nx + w/2, ny);
Chris@430 1119 path.lineTo(nx + w/2, ny);
Chris@6 1120
Chris@1266 1121 } else {
Chris@6 1122
Chris@1266 1123 double x0 = x + double(w)/2;
Chris@1266 1124 double x1 = nx + double(w)/2;
Chris@1266 1125
Chris@1266 1126 double y0 = y;
Chris@1266 1127 double y1 = ny;
Chris@55 1128
Chris@615 1129 if (m_plotStyle == PlotDiscreteCurves) {
Chris@721 1130 bool nextGap =
Chris@721 1131 (nvalue == 0.0) ||
Chris@1474 1132 (nf - p.getFrame() >= model->getResolution() * 2);
Chris@615 1133 if (nextGap) {
Chris@615 1134 x1 = x0;
Chris@615 1135 y1 = y0;
Chris@615 1136 }
Chris@615 1137 }
Chris@615 1138
Chris@1266 1139 if (pointCount == 0 || gap) {
Chris@1266 1140 path.moveTo((x0 + x1) / 2, (y0 + y1) / 2);
Chris@1266 1141 }
Chris@6 1142
Chris@1266 1143 if (nx - x > 5) {
Chris@1266 1144 path.cubicTo(x0, y0,
Chris@1266 1145 x0, y0,
Chris@1266 1146 (x0 + x1) / 2, (y0 + y1) / 2);
Chris@55 1147
Chris@1266 1148 // // or
Chris@1266 1149 // path.quadTo(x0, y0, (x0 + x1) / 2, (y0 + y1) / 2);
Chris@55 1150
Chris@1266 1151 } else {
Chris@883 1152 path.lineTo(x0, y0);
Chris@1266 1153 path.lineTo((x0 + x1) / 2, (y0 + y1) / 2);
Chris@1266 1154 }
Chris@1266 1155 }
Chris@1266 1156 }
Chris@1266 1157 }
Chris@0 1158
Chris@1266 1159 if (m_plotStyle == PlotSegmentation) {
Chris@76 1160
Chris@526 1161 #ifdef DEBUG_TIME_VALUE_LAYER
Chris@682 1162 cerr << "drawing rect" << endl;
Chris@526 1163 #endif
Chris@1266 1164
Chris@1266 1165 if (nx <= x) continue;
Chris@26 1166
Chris@1401 1167 paint.setPen(v->scalePen(QPen(getForegroundQColor(v), 2)));
Chris@559 1168
Chris@631 1169 if (!illuminate) {
Chris@513 1170 if (!m_drawSegmentDivisions ||
Chris@513 1171 nx < x + 5 ||
Chris@918 1172 x >= v->getPaintWidth() - 1) {
Chris@513 1173 paint.setPen(Qt::NoPen);
Chris@513 1174 }
Chris@1266 1175 }
Chris@26 1176
Chris@1266 1177 paint.drawRect(x, -1, nx - x, v->getPaintHeight() + 1);
Chris@1266 1178 }
Chris@26 1179
Chris@741 1180 if (v->shouldShowFeatureLabels()) {
Chris@629 1181
Chris@1429 1182 QString label = p.getLabel();
Chris@741 1183 bool italic = false;
Chris@741 1184
Chris@741 1185 if (label == "" &&
Chris@741 1186 (m_plotStyle == PlotPoints ||
Chris@741 1187 m_plotStyle == PlotSegmentation ||
Chris@741 1188 m_plotStyle == PlotConnectedPoints)) {
Chris@741 1189 char lc[20];
Chris@1429 1190 snprintf(lc, 20, "%.3g", p.getValue());
Chris@741 1191 label = lc;
Chris@741 1192 italic = true;
Chris@741 1193 }
Chris@741 1194
Chris@1474 1195 // Qt 5.13 deprecates QFontMetrics::width(), but its suggested
Chris@1474 1196 // replacement (horizontalAdvance) was only added in Qt 5.11
Chris@1474 1197 // which is too new for us
Chris@1474 1198 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
Chris@1474 1199
Chris@741 1200 if (label != "") {
Chris@741 1201 // Quick test for 20px before we do the slower test using metrics
Chris@741 1202 bool haveRoom = (nx > x + 20);
Chris@741 1203 haveRoom = (haveRoom &&
Chris@741 1204 (nx > x + 6 + paint.fontMetrics().width(label)));
Chris@741 1205 if (haveRoom ||
Chris@741 1206 (!haveNext &&
Chris@741 1207 (pointCount == 0 || !italic))) {
Chris@1429 1208 PaintAssistant::drawVisibleText
Chris@1429 1209 (v, paint, x + 5, textY, label,
Chris@1429 1210 italic ?
Chris@1429 1211 PaintAssistant::OutlinedItalicText :
Chris@1429 1212 PaintAssistant::OutlinedText);
Chris@741 1213 }
Chris@741 1214 }
Chris@629 1215 }
Chris@629 1216
Chris@1429 1217 prevFrame = p.getFrame();
Chris@632 1218 ++pointCount;
Chris@0 1219 }
Chris@6 1220
Chris@691 1221 if (m_plotStyle == PlotDiscreteCurves) {
Chris@691 1222 paint.setRenderHint(QPainter::Antialiasing, true);
Chris@1266 1223 paint.drawPath(path);
Chris@691 1224 } else if ((m_plotStyle == PlotCurve || m_plotStyle == PlotLines)
Chris@691 1225 && !path.isEmpty()) {
Chris@1266 1226 paint.setRenderHint(QPainter::Antialiasing, pointCount <= v->getPaintWidth());
Chris@1266 1227 paint.drawPath(path);
Chris@6 1228 }
Chris@6 1229
Chris@6 1230 paint.restore();
Chris@6 1231
Chris@6 1232 // looks like save/restore doesn't deal with this:
Chris@6 1233 paint.setRenderHint(QPainter::Antialiasing, false);
Chris@6 1234 }
Chris@6 1235
Chris@42 1236 int
Chris@918 1237 TimeValueLayer::getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &paint) const
Chris@42 1238 {
Chris@1474 1239 auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
Chris@1474 1240 if (!model) {
Chris@1315 1241 return 0;
Chris@1315 1242 } else if (shouldAutoAlign() && !valueExtentsMatchMine(v)) {
Chris@701 1243 return 0;
Chris@701 1244 } else if (m_plotStyle == PlotSegmentation) {
Chris@699 1245 if (m_verticalScale == LogScale) {
Chris@699 1246 return LogColourScale().getWidth(v, paint);
Chris@699 1247 } else {
Chris@699 1248 return LinearColourScale().getWidth(v, paint);
Chris@699 1249 }
Chris@698 1250 } else {
Chris@699 1251 if (m_verticalScale == LogScale) {
Chris@699 1252 return LogNumericalScale().getWidth(v, paint) + 10; // for piano
Chris@699 1253 } else {
Chris@699 1254 return LinearNumericalScale().getWidth(v, paint);
Chris@699 1255 }
Chris@698 1256 }
Chris@42 1257 }
Chris@42 1258
Chris@42 1259 void
Chris@918 1260 TimeValueLayer::paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect) const
Chris@42 1261 {
Chris@1474 1262 auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
Chris@1474 1263 if (!model || model->isEmpty()) return;
Chris@42 1264
Chris@699 1265 QString unit;
Chris@908 1266 double min, max;
Chris@698 1267 bool logarithmic;
Chris@698 1268
Chris@698 1269 int w = getVerticalScaleWidth(v, false, paint);
Chris@918 1270 int h = v->getPaintHeight();
Chris@698 1271
Chris@698 1272 if (m_plotStyle == PlotSegmentation) {
Chris@698 1273
Chris@699 1274 getValueExtents(min, max, logarithmic, unit);
Chris@699 1275
Chris@699 1276 if (logarithmic) {
Chris@699 1277 LogRange::mapRange(min, max);
Chris@699 1278 LogColourScale().paintVertical(v, this, paint, 0, min, max);
Chris@699 1279 } else {
Chris@699 1280 LinearColourScale().paintVertical(v, this, paint, 0, min, max);
Chris@699 1281 }
Chris@698 1282
Chris@698 1283 } else {
Chris@698 1284
Chris@699 1285 getScaleExtents(v, min, max, logarithmic);
Chris@699 1286
Chris@698 1287 if (logarithmic) {
Chris@698 1288 LogNumericalScale().paintVertical(v, this, paint, 0, min, max);
Chris@698 1289 } else {
Chris@698 1290 LinearNumericalScale().paintVertical(v, this, paint, 0, min, max);
Chris@698 1291 }
Chris@698 1292
Chris@698 1293 if (logarithmic && (getScaleUnits() == "Hz")) {
Chris@698 1294 PianoScale().paintPianoVertical
Chris@698 1295 (v, paint, QRect(w - 10, 0, 10, h),
Chris@698 1296 LogRange::unmap(min),
Chris@698 1297 LogRange::unmap(max));
Chris@698 1298 paint.drawLine(w, 0, w, h);
Chris@698 1299 }
Chris@698 1300 }
Chris@698 1301
Chris@698 1302 if (getScaleUnits() != "") {
Chris@701 1303 int mw = w - 5;
Chris@701 1304 paint.drawText(5,
Chris@701 1305 5 + paint.fontMetrics().ascent(),
Chris@701 1306 TextAbbrev::abbreviate(getScaleUnits(),
Chris@701 1307 paint.fontMetrics(),
Chris@701 1308 mw));
Chris@691 1309 }
Chris@42 1310 }
Chris@42 1311
Chris@21 1312 void
Chris@918 1313 TimeValueLayer::drawStart(LayerGeometryProvider *v, QMouseEvent *e)
Chris@21 1314 {
Chris@526 1315 #ifdef DEBUG_TIME_VALUE_LAYER
Chris@682 1316 cerr << "TimeValueLayer::drawStart(" << e->x() << "," << e->y() << ")" << endl;
Chris@526 1317 #endif
Chris@21 1318
Chris@1474 1319 auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
Chris@1474 1320 if (!model) return;
Chris@21 1321
Chris@908 1322 sv_frame_t frame = v->getFrameForX(e->x());
Chris@1474 1323 int resolution = model->getResolution();
Chris@21 1324 if (frame < 0) frame = 0;
Chris@76 1325 frame = (frame / resolution) * resolution;
Chris@21 1326
Chris@908 1327 double value = getValueForY(v, e->y());
Chris@21 1328
Chris@76 1329 bool havePoint = false;
Chris@76 1330
Chris@1429 1331 EventVector points = getLocalPoints(v, e->x());
Chris@76 1332 if (!points.empty()) {
Chris@1429 1333 for (EventVector::iterator i = points.begin();
Chris@76 1334 i != points.end(); ++i) {
Chris@1429 1335 if (((i->getFrame() / resolution) * resolution) != frame) {
Chris@526 1336 #ifdef DEBUG_TIME_VALUE_LAYER
Chris@1429 1337 cerr << "ignoring out-of-range frame at " << i->getFrame() << endl;
Chris@526 1338 #endif
Chris@76 1339 continue;
Chris@76 1340 }
Chris@76 1341 m_editingPoint = *i;
Chris@76 1342 havePoint = true;
Chris@76 1343 }
Chris@76 1344 }
Chris@76 1345
Chris@76 1346 if (!havePoint) {
Chris@1429 1347 m_editingPoint = Event(frame, float(value), tr("New Point"));
Chris@76 1348 }
Chris@76 1349
Chris@23 1350 m_originalPoint = m_editingPoint;
Chris@22 1351
Chris@376 1352 if (m_editingCommand) finish(m_editingCommand);
Chris@1474 1353 m_editingCommand = new ChangeEventsCommand(m_model.untyped, tr("Draw Point"));
Chris@76 1354 if (!havePoint) {
Chris@1429 1355 m_editingCommand->add(m_editingPoint);
Chris@76 1356 }
Chris@22 1357
Chris@21 1358 m_editing = true;
Chris@21 1359 }
Chris@21 1360
Chris@21 1361 void
Chris@918 1362 TimeValueLayer::drawDrag(LayerGeometryProvider *v, QMouseEvent *e)
Chris@21 1363 {
Chris@526 1364 #ifdef DEBUG_TIME_VALUE_LAYER
Chris@682 1365 cerr << "TimeValueLayer::drawDrag(" << e->x() << "," << e->y() << ")" << endl;
Chris@526 1366 #endif
Chris@21 1367
Chris@1474 1368 auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
Chris@1474 1369 if (!model || !m_editing) return;
Chris@21 1370
Chris@908 1371 sv_frame_t frame = v->getFrameForX(e->x());
Chris@1474 1372 int resolution = model->getResolution();
Chris@21 1373 if (frame < 0) frame = 0;
Chris@76 1374 frame = (frame / resolution) * resolution;
Chris@21 1375
Chris@908 1376 double value = getValueForY(v, e->y());
Chris@21 1377
Chris@1429 1378 EventVector points = getLocalPoints(v, e->x());
Chris@76 1379
Chris@526 1380 #ifdef DEBUG_TIME_VALUE_LAYER
Chris@682 1381 cerr << points.size() << " points" << endl;
Chris@526 1382 #endif
Chris@76 1383
Chris@76 1384 bool havePoint = false;
Chris@76 1385
Chris@76 1386 if (!points.empty()) {
Chris@1429 1387 for (EventVector::iterator i = points.begin();
Chris@76 1388 i != points.end(); ++i) {
Chris@1429 1389 if (i->getFrame() == m_editingPoint.getFrame() &&
Chris@1429 1390 i->getValue() == m_editingPoint.getValue()) {
Chris@526 1391 #ifdef DEBUG_TIME_VALUE_LAYER
Chris@1429 1392 cerr << "ignoring current editing point at " << i->getFrame() << ", " << i->getValue() << endl;
Chris@526 1393 #endif
Chris@76 1394 continue;
Chris@76 1395 }
Chris@1429 1396 if (((i->getFrame() / resolution) * resolution) != frame) {
Chris@526 1397 #ifdef DEBUG_TIME_VALUE_LAYER
Chris@1429 1398 cerr << "ignoring out-of-range frame at " << i->getFrame() << endl;
Chris@526 1399 #endif
Chris@76 1400 continue;
Chris@76 1401 }
Chris@526 1402 #ifdef DEBUG_TIME_VALUE_LAYER
Chris@1429 1403 cerr << "adjusting to new point at " << i->getFrame() << ", " << i->getValue() << endl;
Chris@526 1404 #endif
Chris@76 1405 m_editingPoint = *i;
Chris@76 1406 m_originalPoint = m_editingPoint;
Chris@1429 1407 m_editingCommand->remove(m_editingPoint);
Chris@76 1408 havePoint = true;
Chris@76 1409 }
Chris@76 1410 }
Chris@76 1411
Chris@76 1412 if (!havePoint) {
Chris@1429 1413 if (frame == m_editingPoint.getFrame()) {
Chris@1429 1414 m_editingCommand->remove(m_editingPoint);
Chris@76 1415 }
Chris@76 1416 }
Chris@76 1417
Chris@1429 1418 m_editingPoint = m_editingPoint
Chris@1429 1419 .withFrame(frame)
Chris@1429 1420 .withValue(float(value));
Chris@1429 1421 m_editingCommand->add(m_editingPoint);
Chris@21 1422 }
Chris@21 1423
Chris@21 1424 void
Chris@918 1425 TimeValueLayer::drawEnd(LayerGeometryProvider *, QMouseEvent *)
Chris@21 1426 {
Chris@526 1427 #ifdef DEBUG_TIME_VALUE_LAYER
Chris@682 1428 cerr << "TimeValueLayer::drawEnd" << endl;
Chris@526 1429 #endif
Chris@1474 1430 auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
Chris@1474 1431 if (!model || !m_editing) return;
Chris@376 1432 finish(m_editingCommand);
Chris@1408 1433 m_editingCommand = nullptr;
Chris@21 1434 m_editing = false;
Chris@21 1435 }
Chris@21 1436
Chris@21 1437 void
Chris@918 1438 TimeValueLayer::eraseStart(LayerGeometryProvider *v, QMouseEvent *e)
Chris@335 1439 {
Chris@1474 1440 auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
Chris@1474 1441 if (!model) return;
Chris@335 1442
Chris@1429 1443 EventVector points = getLocalPoints(v, e->x());
Chris@335 1444 if (points.empty()) return;
Chris@335 1445
Chris@335 1446 m_editingPoint = *points.begin();
Chris@335 1447
Chris@335 1448 if (m_editingCommand) {
Chris@1266 1449 finish(m_editingCommand);
Chris@1408 1450 m_editingCommand = nullptr;
Chris@335 1451 }
Chris@335 1452
Chris@335 1453 m_editing = true;
Chris@335 1454 }
Chris@335 1455
Chris@335 1456 void
Chris@918 1457 TimeValueLayer::eraseDrag(LayerGeometryProvider *, QMouseEvent *)
Chris@335 1458 {
Chris@335 1459 }
Chris@335 1460
Chris@335 1461 void
Chris@918 1462 TimeValueLayer::eraseEnd(LayerGeometryProvider *v, QMouseEvent *e)
Chris@335 1463 {
Chris@1474 1464 auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
Chris@1474 1465 if (!model || !m_editing) return;
Chris@335 1466
Chris@335 1467 m_editing = false;
Chris@335 1468
Chris@1429 1469 EventVector points = getLocalPoints(v, e->x());
Chris@335 1470 if (points.empty()) return;
Chris@1429 1471 if (points.begin()->getFrame() != m_editingPoint.getFrame() ||
Chris@1429 1472 points.begin()->getValue() != m_editingPoint.getValue()) return;
Chris@335 1473
Chris@1474 1474 m_editingCommand = new ChangeEventsCommand(m_model.untyped, tr("Erase Point"));
Chris@1429 1475 m_editingCommand->remove(m_editingPoint);
Chris@376 1476 finish(m_editingCommand);
Chris@1408 1477 m_editingCommand = nullptr;
Chris@335 1478 m_editing = false;
Chris@335 1479 }
Chris@335 1480
Chris@335 1481 void
Chris@918 1482 TimeValueLayer::editStart(LayerGeometryProvider *v, QMouseEvent *e)
Chris@21 1483 {
Chris@526 1484 #ifdef DEBUG_TIME_VALUE_LAYER
Chris@682 1485 cerr << "TimeValueLayer::editStart(" << e->x() << "," << e->y() << ")" << endl;
Chris@526 1486 #endif
Chris@21 1487
Chris@1474 1488 auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
Chris@1474 1489 if (!model) return;
Chris@21 1490
Chris@1429 1491 EventVector points = getLocalPoints(v, e->x());
Chris@21 1492 if (points.empty()) return;
Chris@21 1493
Chris@21 1494 m_editingPoint = *points.begin();
Chris@23 1495 m_originalPoint = m_editingPoint;
Chris@22 1496
Chris@22 1497 if (m_editingCommand) {
Chris@1266 1498 finish(m_editingCommand);
Chris@1408 1499 m_editingCommand = nullptr;
Chris@22 1500 }
Chris@22 1501
Chris@21 1502 m_editing = true;
Chris@21 1503 }
Chris@21 1504
Chris@21 1505 void
Chris@918 1506 TimeValueLayer::editDrag(LayerGeometryProvider *v, QMouseEvent *e)
Chris@21 1507 {
Chris@526 1508 #ifdef DEBUG_TIME_VALUE_LAYER
Chris@682 1509 cerr << "TimeValueLayer::editDrag(" << e->x() << "," << e->y() << ")" << endl;
Chris@526 1510 #endif
Chris@21 1511
Chris@1474 1512 auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
Chris@1474 1513 if (!model || !m_editing) return;
Chris@21 1514
Chris@908 1515 sv_frame_t frame = v->getFrameForX(e->x());
Chris@21 1516 if (frame < 0) frame = 0;
Chris@1474 1517 frame = frame / model->getResolution() * model->getResolution();
Chris@21 1518
Chris@908 1519 double value = getValueForY(v, e->y());
Chris@21 1520
Chris@22 1521 if (!m_editingCommand) {
Chris@1474 1522 m_editingCommand = new ChangeEventsCommand(m_model.untyped, tr("Drag Point"));
Chris@22 1523 }
Chris@22 1524
Chris@1429 1525 m_editingCommand->remove(m_editingPoint);
Chris@1429 1526 m_editingPoint = m_editingPoint
Chris@1429 1527 .withFrame(frame)
Chris@1429 1528 .withValue(float(value));
Chris@1429 1529 m_editingCommand->add(m_editingPoint);
Chris@21 1530 }
Chris@21 1531
Chris@21 1532 void
Chris@918 1533 TimeValueLayer::editEnd(LayerGeometryProvider *, QMouseEvent *)
Chris@21 1534 {
Chris@526 1535 #ifdef DEBUG_TIME_VALUE_LAYER
Chris@682 1536 cerr << "TimeValueLayer::editEnd" << endl;
Chris@526 1537 #endif
Chris@1474 1538 auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
Chris@1474 1539 if (!model || !m_editing) return;
Chris@23 1540
Chris@23 1541 if (m_editingCommand) {
Chris@23 1542
Chris@1266 1543 QString newName = m_editingCommand->getName();
Chris@23 1544
Chris@1429 1545 if (m_editingPoint.getFrame() != m_originalPoint.getFrame()) {
Chris@1429 1546 if (m_editingPoint.getValue() != m_originalPoint.getValue()) {
Chris@1266 1547 newName = tr("Edit Point");
Chris@1266 1548 } else {
Chris@1266 1549 newName = tr("Relocate Point");
Chris@1266 1550 }
Chris@1266 1551 } else {
Chris@1266 1552 newName = tr("Change Point Value");
Chris@1266 1553 }
Chris@23 1554
Chris@1266 1555 m_editingCommand->setName(newName);
Chris@1266 1556 finish(m_editingCommand);
Chris@23 1557 }
Chris@23 1558
Chris@1408 1559 m_editingCommand = nullptr;
Chris@21 1560 m_editing = false;
Chris@21 1561 }
Chris@21 1562
Chris@255 1563 bool
Chris@918 1564 TimeValueLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e)
Chris@70 1565 {
Chris@1474 1566 auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
Chris@1474 1567 if (!model) return false;
Chris@70 1568
Chris@1429 1569 EventVector points = getLocalPoints(v, e->x());
Chris@255 1570 if (points.empty()) return false;
Chris@70 1571
Chris@1429 1572 Event point = *points.begin();
Chris@70 1573
Chris@70 1574 ItemEditDialog *dialog = new ItemEditDialog
Chris@1474 1575 (model->getSampleRate(),
Chris@70 1576 ItemEditDialog::ShowTime |
Chris@70 1577 ItemEditDialog::ShowValue |
Chris@73 1578 ItemEditDialog::ShowText,
Chris@698 1579 getScaleUnits());
Chris@70 1580
Chris@1429 1581 dialog->setFrameTime(point.getFrame());
Chris@1429 1582 dialog->setValue(point.getValue());
Chris@1429 1583 dialog->setText(point.getLabel());
Chris@70 1584
Chris@70 1585 if (dialog->exec() == QDialog::Accepted) {
Chris@70 1586
Chris@1429 1587 Event newPoint = point
Chris@1429 1588 .withFrame(dialog->getFrameTime())
Chris@1429 1589 .withValue(dialog->getValue())
Chris@1429 1590 .withLabel(dialog->getText());
Chris@70 1591
Chris@1429 1592 ChangeEventsCommand *command =
Chris@1474 1593 new ChangeEventsCommand(m_model.untyped, tr("Edit Point"));
Chris@1429 1594 command->remove(point);
Chris@1429 1595 command->add(newPoint);
Chris@376 1596 finish(command);
Chris@70 1597 }
Chris@70 1598
Chris@70 1599 delete dialog;
Chris@255 1600 return true;
Chris@70 1601 }
Chris@70 1602
Chris@70 1603 void
Chris@908 1604 TimeValueLayer::moveSelection(Selection s, sv_frame_t newStartFrame)
Chris@43 1605 {
Chris@1474 1606 auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
Chris@1474 1607 if (!model) return;
Chris@99 1608
Chris@1429 1609 ChangeEventsCommand *command =
Chris@1474 1610 new ChangeEventsCommand(m_model.untyped, tr("Drag Selection"));
Chris@43 1611
Chris@1429 1612 EventVector points =
Chris@1474 1613 model->getEventsWithin(s.getStartFrame(), s.getDuration());
Chris@43 1614
Chris@1429 1615 for (Event p: points) {
Chris@43 1616
Chris@1429 1617 Event newPoint = p.withFrame
Chris@1429 1618 (p.getFrame() + newStartFrame - s.getStartFrame());
Chris@1429 1619 command->remove(p);
Chris@1429 1620 command->add(newPoint);
Chris@43 1621 }
Chris@43 1622
Chris@376 1623 finish(command);
Chris@43 1624 }
Chris@43 1625
Chris@43 1626 void
Chris@43 1627 TimeValueLayer::resizeSelection(Selection s, Selection newSize)
Chris@43 1628 {
Chris@1474 1629 auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
Chris@1474 1630 if (!model || !s.getDuration()) return;
Chris@99 1631
Chris@1429 1632 ChangeEventsCommand *command =
Chris@1474 1633 new ChangeEventsCommand(m_model.untyped, tr("Resize Selection"));
Chris@43 1634
Chris@1429 1635 EventVector points =
Chris@1474 1636 model->getEventsWithin(s.getStartFrame(), s.getDuration());
Chris@43 1637
Chris@1429 1638 double ratio = double(newSize.getDuration()) / double(s.getDuration());
Chris@1429 1639 double oldStart = double(s.getStartFrame());
Chris@1429 1640 double newStart = double(newSize.getStartFrame());
Chris@43 1641
Chris@1429 1642 for (Event p: points) {
Chris@1429 1643
Chris@1429 1644 double newFrame = (double(p.getFrame()) - oldStart) * ratio + newStart;
Chris@43 1645
Chris@1429 1646 Event newPoint = p
Chris@1429 1647 .withFrame(lrint(newFrame));
Chris@1429 1648 command->remove(p);
Chris@1429 1649 command->add(newPoint);
Chris@43 1650 }
Chris@43 1651
Chris@376 1652 finish(command);
Chris@43 1653 }
Chris@43 1654
Chris@76 1655 void
Chris@76 1656 TimeValueLayer::deleteSelection(Selection s)
Chris@76 1657 {
Chris@1474 1658 auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
Chris@1474 1659 if (!model) return;
Chris@99 1660
Chris@1429 1661 ChangeEventsCommand *command =
Chris@1474 1662 new ChangeEventsCommand(m_model.untyped, tr("Delete Selected Points"));
Chris@76 1663
Chris@1429 1664 EventVector points =
Chris@1474 1665 model->getEventsWithin(s.getStartFrame(), s.getDuration());
Chris@76 1666
Chris@1429 1667 for (Event p: points) {
Chris@1429 1668 command->remove(p);
Chris@76 1669 }
Chris@76 1670
Chris@376 1671 finish(command);
Chris@76 1672 }
Chris@76 1673
Chris@76 1674 void
Chris@918 1675 TimeValueLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to)
Chris@76 1676 {
Chris@1474 1677 auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
Chris@1474 1678 if (!model) return;
Chris@99 1679
Chris@1429 1680 EventVector points =
Chris@1474 1681 model->getEventsWithin(s.getStartFrame(), s.getDuration());
Chris@76 1682
Chris@1429 1683 for (Event p: points) {
Chris@1429 1684 to.addPoint(p.withReferenceFrame(alignToReference(v, p.getFrame())));
Chris@76 1685 }
Chris@76 1686 }
Chris@76 1687
Chris@125 1688 bool
Chris@1533 1689 TimeValueLayer::paste(LayerGeometryProvider *v, const Clipboard &from,
Chris@1533 1690 sv_frame_t /* frameOffset */, bool interactive)
Chris@76 1691 {
Chris@1474 1692 auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
Chris@1474 1693 if (!model) return false;
Chris@99 1694
Chris@1433 1695 EventVector points = from.getPoints();
Chris@76 1696
Chris@360 1697 bool realign = false;
Chris@360 1698
Chris@360 1699 if (clipboardHasDifferentAlignment(v, from)) {
Chris@360 1700
Chris@360 1701 QMessageBox::StandardButton button =
Chris@919 1702 QMessageBox::question(v->getView(), tr("Re-align pasted items?"),
Chris@360 1703 tr("The items 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 1704 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
Chris@360 1705 QMessageBox::Yes);
Chris@360 1706
Chris@360 1707 if (button == QMessageBox::Cancel) {
Chris@360 1708 return false;
Chris@360 1709 }
Chris@360 1710
Chris@360 1711 if (button == QMessageBox::Yes) {
Chris@360 1712 realign = true;
Chris@360 1713 }
Chris@360 1714 }
Chris@360 1715
Chris@1429 1716 ChangeEventsCommand *command =
Chris@1474 1717 new ChangeEventsCommand(m_model.untyped, tr("Paste"));
Chris@76 1718
Chris@125 1719 enum ValueAvailability {
Chris@125 1720 UnknownAvailability,
Chris@125 1721 NoValues,
Chris@125 1722 SomeValues,
Chris@125 1723 AllValues
Chris@125 1724 };
Chris@125 1725
Chris@340 1726 Labeller::ValueType generation = Labeller::ValueNone;
Chris@125 1727
Chris@125 1728 bool haveUsableLabels = false;
Chris@340 1729 Labeller labeller;
Chris@1474 1730 labeller.setSampleRate(model->getSampleRate());
Chris@125 1731
Chris@125 1732 if (interactive) {
Chris@125 1733
Chris@125 1734 ValueAvailability availability = UnknownAvailability;
Chris@125 1735
Chris@1423 1736 for (EventVector::const_iterator i = points.begin();
Chris@125 1737 i != points.end(); ++i) {
Chris@125 1738
Chris@125 1739 if (availability == UnknownAvailability) {
Chris@1423 1740 if (i->hasValue()) availability = AllValues;
Chris@125 1741 else availability = NoValues;
Chris@125 1742 continue;
Chris@125 1743 }
Chris@125 1744
Chris@1423 1745 if (i->hasValue()) {
Chris@125 1746 if (availability == NoValues) {
Chris@125 1747 availability = SomeValues;
Chris@125 1748 }
Chris@125 1749 } else {
Chris@125 1750 if (availability == AllValues) {
Chris@125 1751 availability = SomeValues;
Chris@125 1752 }
Chris@125 1753 }
Chris@125 1754
Chris@125 1755 if (!haveUsableLabels) {
Chris@1423 1756 if (i->hasLabel()) {
Chris@125 1757 if (i->getLabel().contains(QRegExp("[0-9]"))) {
Chris@125 1758 haveUsableLabels = true;
Chris@125 1759 }
Chris@125 1760 }
Chris@125 1761 }
Chris@125 1762
Chris@125 1763 if (availability == SomeValues && haveUsableLabels) break;
Chris@125 1764 }
Chris@125 1765
Chris@125 1766 if (availability == NoValues || availability == SomeValues) {
Chris@125 1767
Chris@125 1768 QString text;
Chris@125 1769 if (availability == NoValues) {
Chris@125 1770 text = tr("The items you are pasting do not have values.\nWhat values do you want to use for these items?");
Chris@125 1771 } else {
Chris@125 1772 text = tr("Some of the items you are pasting do not have values.\nWhat values do you want to use for these items?");
Chris@125 1773 }
Chris@125 1774
Chris@340 1775 Labeller::TypeNameMap names = labeller.getTypeNames();
Chris@340 1776
Chris@125 1777 QStringList options;
Chris@340 1778 std::vector<Labeller::ValueType> genopts;
Chris@125 1779
Chris@340 1780 for (Labeller::TypeNameMap::const_iterator i = names.begin();
Chris@340 1781 i != names.end(); ++i) {
Chris@340 1782 if (i->first == Labeller::ValueNone) options << tr("Zero for all items");
Chris@340 1783 else options << i->second;
Chris@340 1784 genopts.push_back(i->first);
Chris@125 1785 }
Chris@125 1786
Chris@125 1787 static int prevSelection = 0;
Chris@125 1788
Chris@125 1789 bool ok = false;
Chris@125 1790 QString selected = ListInputDialog::getItem
Chris@1408 1791 (nullptr, tr("Choose value calculation"),
Chris@125 1792 text, options, prevSelection, &ok);
Chris@125 1793
Chris@852 1794 if (!ok) {
Chris@852 1795 delete command;
Chris@852 1796 return false;
Chris@852 1797 }
Chris@125 1798 int selection = 0;
Chris@340 1799 generation = Labeller::ValueNone;
Chris@125 1800
Chris@125 1801 for (QStringList::const_iterator i = options.begin();
Chris@125 1802 i != options.end(); ++i) {
Chris@125 1803 if (selected == *i) {
Chris@340 1804 generation = genopts[selection];
Chris@125 1805 break;
Chris@125 1806 }
Chris@125 1807 ++selection;
Chris@125 1808 }
Chris@340 1809
Chris@340 1810 labeller.setType(generation);
Chris@340 1811
Chris@340 1812 if (generation == Labeller::ValueFromCyclicalCounter ||
Chris@340 1813 generation == Labeller::ValueFromTwoLevelCounter) {
Chris@616 1814 int cycleSize = QInputDialog::getInt
Chris@1408 1815 (nullptr, tr("Select cycle size"),
Chris@340 1816 tr("Cycle size:"), 4, 2, 16, 1);
Chris@340 1817 labeller.setCounterCycleSize(cycleSize);
Chris@340 1818 }
Chris@125 1819
Chris@125 1820 prevSelection = selection;
Chris@125 1821 }
Chris@125 1822 }
Chris@125 1823
Chris@1429 1824 Event prevPoint;
Chris@125 1825
Chris@1423 1826 for (EventVector::const_iterator i = points.begin();
Chris@76 1827 i != points.end(); ++i) {
Chris@76 1828
Chris@908 1829 sv_frame_t frame = 0;
Chris@360 1830
Chris@360 1831 if (!realign) {
Chris@360 1832
Chris@360 1833 frame = i->getFrame();
Chris@360 1834
Chris@360 1835 } else {
Chris@360 1836
Chris@1423 1837 if (i->hasReferenceFrame()) {
Chris@360 1838 frame = i->getReferenceFrame();
Chris@360 1839 frame = alignFromReference(v, frame);
Chris@360 1840 } else {
Chris@360 1841 frame = i->getFrame();
Chris@360 1842 }
Chris@76 1843 }
Chris@360 1844
Chris@1533 1845 Event newPoint = i->withFrame(frame);
Chris@1533 1846
Chris@1429 1847 if (!i->hasLabel() && i->hasValue()) {
Chris@1429 1848 newPoint = newPoint.withLabel(QString("%1").arg(i->getValue()));
Chris@125 1849 }
Chris@125 1850
Chris@372 1851 bool usePrev = false;
Chris@1429 1852 Event formerPrevPoint = prevPoint;
Chris@372 1853
Chris@1429 1854 if (!i->hasValue()) {
Chris@526 1855 #ifdef DEBUG_TIME_VALUE_LAYER
Chris@1429 1856 cerr << "Setting value on point at " << newPoint.getFrame() << " from labeller";
Chris@526 1857 if (i == points.begin()) {
Chris@682 1858 cerr << ", no prev point" << endl;
Chris@526 1859 } else {
Chris@1429 1860 cerr << ", prev point is at " << prevPoint.getFrame() << endl;
Chris@526 1861 }
Chris@526 1862 #endif
Chris@1429 1863
Chris@1429 1864 Labeller::Revaluing valuing =
Chris@1429 1865 labeller.revalue
Chris@1408 1866 (newPoint, (i == points.begin()) ? nullptr : &prevPoint);
Chris@1429 1867
Chris@526 1868 #ifdef DEBUG_TIME_VALUE_LAYER
Chris@1429 1869 cerr << "New point value = " << newPoint.getValue() << endl;
Chris@526 1870 #endif
Chris@1429 1871 if (valuing.first == Labeller::AppliesToPreviousEvent) {
Chris@372 1872 usePrev = true;
Chris@1429 1873 prevPoint = valuing.second;
Chris@1429 1874 } else {
Chris@1429 1875 newPoint = valuing.second;
Chris@372 1876 }
Chris@372 1877 }
Chris@372 1878
Chris@372 1879 if (usePrev) {
Chris@1429 1880 command->remove(formerPrevPoint);
Chris@1429 1881 command->add(prevPoint);
Chris@340 1882 }
Chris@125 1883
Chris@340 1884 prevPoint = newPoint;
Chris@1429 1885 command->add(newPoint);
Chris@76 1886 }
Chris@76 1887
Chris@376 1888 finish(command);
Chris@125 1889 return true;
Chris@360 1890 }
Chris@76 1891
Chris@316 1892 void
Chris@316 1893 TimeValueLayer::toXml(QTextStream &stream,
Chris@316 1894 QString indent, QString extraAttributes) const
Chris@6 1895 {
Chris@1362 1896 QString s;
Chris@1362 1897
Chris@1362 1898 s += QString("plotStyle=\"%1\" "
Chris@1362 1899 "verticalScale=\"%2\" "
Chris@1362 1900 "scaleMinimum=\"%3\" "
Chris@1362 1901 "scaleMaximum=\"%4\" "
Chris@1362 1902 "drawDivisions=\"%5\" "
Chris@1362 1903 "derivative=\"%6\" ")
Chris@1362 1904 .arg(m_plotStyle)
Chris@1362 1905 .arg(m_verticalScale)
Chris@1362 1906 .arg(m_scaleMinimum)
Chris@1362 1907 .arg(m_scaleMaximum)
Chris@1362 1908 .arg(m_drawSegmentDivisions ? "true" : "false")
Chris@1362 1909 .arg(m_derivative ? "true" : "false");
Chris@1362 1910
Chris@1362 1911 // New-style colour map attribute, by string id rather than by
Chris@1362 1912 // number
Chris@1362 1913
Chris@1362 1914 s += QString("fillColourMap=\"%1\" ")
Chris@1362 1915 .arg(ColourMapper::getColourMapId(m_colourMap));
Chris@1362 1916
Chris@1362 1917 // Old-style colour map attribute
Chris@1362 1918
Chris@1362 1919 s += QString("colourMap=\"%1\" ")
Chris@1362 1920 .arg(ColourMapper::getBackwardCompatibilityColourMap(m_colourMap));
Chris@1362 1921
Chris@1362 1922 SingleColourLayer::toXml(stream, indent, extraAttributes + " " + s);
Chris@0 1923 }
Chris@0 1924
Chris@11 1925 void
Chris@11 1926 TimeValueLayer::setProperties(const QXmlAttributes &attributes)
Chris@11 1927 {
Chris@287 1928 SingleColourLayer::setProperties(attributes);
Chris@11 1929
Chris@445 1930 bool ok, alsoOk;
Chris@287 1931
Chris@1362 1932 QString colourMapId = attributes.value("fillColourMap");
Chris@1362 1933 int colourMap = ColourMapper::getColourMapById(colourMapId);
Chris@1362 1934 if (colourMap >= 0) {
Chris@1362 1935 setFillColourMap(colourMap);
Chris@1362 1936 } else {
Chris@1362 1937 colourMap = attributes.value("colourMap").toInt(&ok);
Chris@1362 1938 if (ok && colourMap < ColourMapper::getColourMapCount()) {
Chris@1362 1939 setFillColourMap(colourMap);
Chris@1362 1940 }
Chris@1362 1941 }
Chris@287 1942
Chris@11 1943 PlotStyle style = (PlotStyle)
Chris@1266 1944 attributes.value("plotStyle").toInt(&ok);
Chris@11 1945 if (ok) setPlotStyle(style);
Chris@286 1946
Chris@286 1947 VerticalScale scale = (VerticalScale)
Chris@1266 1948 attributes.value("verticalScale").toInt(&ok);
Chris@286 1949 if (ok) setVerticalScale(scale);
Chris@445 1950
Chris@513 1951 bool draw = (attributes.value("drawDivisions").trimmed() == "true");
Chris@513 1952 setDrawSegmentDivisions(draw);
Chris@513 1953
Chris@553 1954 bool derivative = (attributes.value("derivative").trimmed() == "true");
Chris@553 1955 setShowDerivative(derivative);
Chris@553 1956
Chris@445 1957 float min = attributes.value("scaleMinimum").toFloat(&ok);
Chris@445 1958 float max = attributes.value("scaleMaximum").toFloat(&alsoOk);
Chris@526 1959 #ifdef DEBUG_TIME_VALUE_LAYER
Chris@682 1960 cerr << "from properties: min = " << min << ", max = " << max << endl;
Chris@526 1961 #endif
Chris@526 1962 if (ok && alsoOk && min != max) setDisplayExtents(min, max);
Chris@11 1963 }
Chris@11 1964