annotate layer/RegionLayer.cpp @ 1432:5b9692768beb single-point

Further snap fixes
author Chris Cannam
date Wed, 20 Mar 2019 15:46:17 +0000
parents 8a7c82282fbc
children 696e569ff21b
rev   line source
Chris@411 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@411 2
Chris@411 3 /*
Chris@411 4 Sonic Visualiser
Chris@411 5 An audio file viewer and annotation editor.
Chris@411 6 Centre for Digital Music, Queen Mary, University of London.
Chris@411 7 This file copyright 2006-2008 Chris Cannam and QMUL.
Chris@411 8
Chris@411 9 This program is free software; you can redistribute it and/or
Chris@411 10 modify it under the terms of the GNU General Public License as
Chris@411 11 published by the Free Software Foundation; either version 2 of the
Chris@411 12 License, or (at your option) any later version. See the file
Chris@411 13 COPYING included with this distribution for more information.
Chris@411 14 */
Chris@411 15
Chris@411 16 #include "RegionLayer.h"
Chris@411 17
Chris@411 18 #include "data/model/Model.h"
Chris@411 19 #include "base/RealTime.h"
Chris@411 20 #include "base/Profiler.h"
Chris@411 21 #include "base/LogRange.h"
Chris@1078 22
Chris@411 23 #include "ColourDatabase.h"
Chris@427 24 #include "ColourMapper.h"
Chris@701 25 #include "LinearNumericalScale.h"
Chris@701 26 #include "LogNumericalScale.h"
Chris@701 27 #include "LinearColourScale.h"
Chris@701 28 #include "LogColourScale.h"
Chris@1078 29 #include "PaintAssistant.h"
Chris@701 30
Chris@411 31 #include "view/View.h"
Chris@411 32
Chris@411 33 #include "data/model/RegionModel.h"
Chris@411 34
Chris@411 35 #include "widgets/ItemEditDialog.h"
Chris@701 36 #include "widgets/TextAbbrev.h"
Chris@411 37
Chris@411 38 #include <QPainter>
Chris@411 39 #include <QPainterPath>
Chris@411 40 #include <QMouseEvent>
Chris@411 41 #include <QTextStream>
Chris@411 42 #include <QMessageBox>
Chris@411 43
Chris@411 44 #include <iostream>
Chris@411 45 #include <cmath>
Chris@411 46
Chris@411 47 RegionLayer::RegionLayer() :
Chris@411 48 SingleColourLayer(),
Chris@1408 49 m_model(nullptr),
Chris@411 50 m_editing(false),
Chris@846 51 m_dragPointX(0),
Chris@846 52 m_dragPointY(0),
Chris@846 53 m_dragStartX(0),
Chris@846 54 m_dragStartY(0),
Chris@543 55 m_originalPoint(0, 0.0, 0, tr("New Region")),
Chris@543 56 m_editingPoint(0, 0.0, 0, tr("New Region")),
Chris@1408 57 m_editingCommand(nullptr),
Chris@433 58 m_verticalScale(EqualSpaced),
Chris@427 59 m_colourMap(0),
Chris@1362 60 m_colourInverted(false),
Chris@412 61 m_plotStyle(PlotLines)
Chris@411 62 {
Chris@411 63
Chris@411 64 }
Chris@411 65
Chris@411 66 void
Chris@411 67 RegionLayer::setModel(RegionModel *model)
Chris@411 68 {
Chris@411 69 if (m_model == model) return;
Chris@411 70 m_model = model;
Chris@411 71
Chris@411 72 connectSignals(m_model);
Chris@411 73
Chris@433 74 connect(m_model, SIGNAL(modelChanged()), this, SLOT(recalcSpacing()));
Chris@433 75 recalcSpacing();
Chris@433 76
Chris@587 77 // SVDEBUG << "RegionLayer::setModel(" << model << ")" << endl;
Chris@411 78
Chris@610 79 if (m_model && m_model->getRDFTypeURI().endsWith("Segment")) {
Chris@610 80 setPlotStyle(PlotSegmentation);
Chris@610 81 }
Chris@610 82 if (m_model && m_model->getRDFTypeURI().endsWith("Change")) {
Chris@610 83 setPlotStyle(PlotSegmentation);
Chris@610 84 }
Chris@610 85
Chris@411 86 emit modelReplaced();
Chris@411 87 }
Chris@411 88
Chris@411 89 Layer::PropertyList
Chris@411 90 RegionLayer::getProperties() const
Chris@411 91 {
Chris@411 92 PropertyList list = SingleColourLayer::getProperties();
Chris@411 93 list.push_back("Vertical Scale");
Chris@411 94 list.push_back("Scale Units");
Chris@412 95 list.push_back("Plot Type");
Chris@411 96 return list;
Chris@411 97 }
Chris@411 98
Chris@411 99 QString
Chris@411 100 RegionLayer::getPropertyLabel(const PropertyName &name) const
Chris@411 101 {
Chris@411 102 if (name == "Vertical Scale") return tr("Vertical Scale");
Chris@411 103 if (name == "Scale Units") return tr("Scale Units");
Chris@412 104 if (name == "Plot Type") return tr("Plot Type");
Chris@411 105 return SingleColourLayer::getPropertyLabel(name);
Chris@411 106 }
Chris@411 107
Chris@411 108 Layer::PropertyType
Chris@411 109 RegionLayer::getPropertyType(const PropertyName &name) const
Chris@411 110 {
Chris@411 111 if (name == "Scale Units") return UnitsProperty;
Chris@411 112 if (name == "Vertical Scale") return ValueProperty;
Chris@412 113 if (name == "Plot Type") return ValueProperty;
Chris@427 114 if (name == "Colour" && m_plotStyle == PlotSegmentation) return ValueProperty;
Chris@411 115 return SingleColourLayer::getPropertyType(name);
Chris@411 116 }
Chris@411 117
Chris@411 118 QString
Chris@411 119 RegionLayer::getPropertyGroupName(const PropertyName &name) const
Chris@411 120 {
Chris@411 121 if (name == "Vertical Scale" || name == "Scale Units") {
Chris@411 122 return tr("Scale");
Chris@411 123 }
Chris@411 124 return SingleColourLayer::getPropertyGroupName(name);
Chris@411 125 }
Chris@411 126
Chris@411 127 int
Chris@411 128 RegionLayer::getPropertyRangeAndValue(const PropertyName &name,
Chris@411 129 int *min, int *max, int *deflt) const
Chris@411 130 {
Chris@411 131 int val = 0;
Chris@411 132
Chris@427 133 if (name == "Colour" && m_plotStyle == PlotSegmentation) {
Chris@427 134
Chris@427 135 if (min) *min = 0;
Chris@427 136 if (max) *max = ColourMapper::getColourMapCount() - 1;
Chris@427 137 if (deflt) *deflt = 0;
Chris@427 138
Chris@427 139 val = m_colourMap;
Chris@427 140
Chris@427 141 } else if (name == "Plot Type") {
Chris@1266 142
Chris@1266 143 if (min) *min = 0;
Chris@1266 144 if (max) *max = 1;
Chris@412 145 if (deflt) *deflt = 0;
Chris@1266 146
Chris@1266 147 val = int(m_plotStyle);
Chris@412 148
Chris@412 149 } else if (name == "Vertical Scale") {
Chris@1266 150
Chris@1266 151 if (min) *min = 0;
Chris@1266 152 if (max) *max = 3;
Chris@433 153 if (deflt) *deflt = int(EqualSpaced);
Chris@1266 154
Chris@1266 155 val = int(m_verticalScale);
Chris@411 156
Chris@411 157 } else if (name == "Scale Units") {
Chris@411 158
Chris@411 159 if (deflt) *deflt = 0;
Chris@411 160 if (m_model) {
Chris@411 161 val = UnitDatabase::getInstance()->getUnitId
Chris@701 162 (getScaleUnits());
Chris@411 163 }
Chris@411 164
Chris@411 165 } else {
Chris@411 166
Chris@1266 167 val = SingleColourLayer::getPropertyRangeAndValue(name, min, max, deflt);
Chris@411 168 }
Chris@411 169
Chris@411 170 return val;
Chris@411 171 }
Chris@411 172
Chris@411 173 QString
Chris@411 174 RegionLayer::getPropertyValueLabel(const PropertyName &name,
Chris@427 175 int value) const
Chris@411 176 {
Chris@427 177 if (name == "Colour" && m_plotStyle == PlotSegmentation) {
Chris@1362 178 return ColourMapper::getColourMapLabel(value);
Chris@427 179 } else if (name == "Plot Type") {
Chris@412 180
Chris@1266 181 switch (value) {
Chris@1266 182 default:
Chris@1266 183 case 0: return tr("Bars");
Chris@1266 184 case 1: return tr("Segmentation");
Chris@1266 185 }
Chris@412 186
Chris@412 187 } else if (name == "Vertical Scale") {
Chris@1266 188 switch (value) {
Chris@1266 189 default:
Chris@1266 190 case 0: return tr("Auto-Align");
Chris@1266 191 case 1: return tr("Equal Spaced");
Chris@1266 192 case 2: return tr("Linear");
Chris@1266 193 case 3: return tr("Log");
Chris@1266 194 }
Chris@411 195 }
Chris@411 196 return SingleColourLayer::getPropertyValueLabel(name, value);
Chris@411 197 }
Chris@411 198
Chris@411 199 void
Chris@411 200 RegionLayer::setProperty(const PropertyName &name, int value)
Chris@411 201 {
Chris@427 202 if (name == "Colour" && m_plotStyle == PlotSegmentation) {
Chris@427 203 setFillColourMap(value);
Chris@427 204 } else if (name == "Plot Type") {
Chris@1266 205 setPlotStyle(PlotStyle(value));
Chris@412 206 } else if (name == "Vertical Scale") {
Chris@1266 207 setVerticalScale(VerticalScale(value));
Chris@411 208 } else if (name == "Scale Units") {
Chris@411 209 if (m_model) {
Chris@411 210 m_model->setScaleUnits
Chris@411 211 (UnitDatabase::getInstance()->getUnitById(value));
Chris@411 212 emit modelChanged();
Chris@411 213 }
Chris@411 214 } else {
Chris@411 215 return SingleColourLayer::setProperty(name, value);
Chris@411 216 }
Chris@411 217 }
Chris@411 218
Chris@411 219 void
Chris@427 220 RegionLayer::setFillColourMap(int map)
Chris@427 221 {
Chris@427 222 if (m_colourMap == map) return;
Chris@427 223 m_colourMap = map;
Chris@427 224 emit layerParametersChanged();
Chris@427 225 }
Chris@427 226
Chris@427 227 void
Chris@412 228 RegionLayer::setPlotStyle(PlotStyle style)
Chris@412 229 {
Chris@412 230 if (m_plotStyle == style) return;
Chris@427 231 bool colourTypeChanged = (style == PlotSegmentation ||
Chris@427 232 m_plotStyle == PlotSegmentation);
Chris@412 233 m_plotStyle = style;
Chris@427 234 if (colourTypeChanged) {
Chris@427 235 emit layerParameterRangesChanged();
Chris@427 236 }
Chris@412 237 emit layerParametersChanged();
Chris@412 238 }
Chris@412 239
Chris@412 240 void
Chris@411 241 RegionLayer::setVerticalScale(VerticalScale scale)
Chris@411 242 {
Chris@411 243 if (m_verticalScale == scale) return;
Chris@411 244 m_verticalScale = scale;
Chris@411 245 emit layerParametersChanged();
Chris@411 246 }
Chris@411 247
Chris@411 248 bool
Chris@918 249 RegionLayer::isLayerScrollable(const LayerGeometryProvider *v) const
Chris@411 250 {
Chris@411 251 QPoint discard;
Chris@411 252 return !v->shouldIlluminateLocalFeatures(this, discard);
Chris@411 253 }
Chris@411 254
Chris@433 255 void
Chris@433 256 RegionLayer::recalcSpacing()
Chris@433 257 {
Chris@433 258 m_spacingMap.clear();
Chris@551 259 m_distributionMap.clear();
Chris@433 260 if (!m_model) return;
Chris@433 261
Chris@587 262 // SVDEBUG << "RegionLayer::recalcSpacing" << endl;
Chris@433 263
Chris@1428 264 EventVector allEvents = m_model->getAllEvents();
Chris@1428 265 for (const Event &e: allEvents) {
Chris@1428 266 m_distributionMap[e.getValue()]++;
Chris@1428 267 // SVDEBUG << "RegionLayer::recalcSpacing: value found: " << e.getValue() << " (now have " << m_distributionMap[e.getValue()] << " of this value)" << endl;
Chris@433 268 }
Chris@433 269
Chris@433 270 int n = 0;
Chris@433 271
Chris@551 272 for (SpacingMap::const_iterator i = m_distributionMap.begin();
Chris@551 273 i != m_distributionMap.end(); ++i) {
Chris@551 274 m_spacingMap[i->first] = n++;
Chris@587 275 // SVDEBUG << "RegionLayer::recalcSpacing: " << i->first << " -> " << m_spacingMap[i->first] << endl;
Chris@433 276 }
Chris@433 277 }
Chris@433 278
Chris@411 279 bool
Chris@905 280 RegionLayer::getValueExtents(double &min, double &max,
Chris@411 281 bool &logarithmic, QString &unit) const
Chris@411 282 {
Chris@411 283 if (!m_model) return false;
Chris@411 284 min = m_model->getValueMinimum();
Chris@411 285 max = m_model->getValueMaximum();
Chris@701 286 unit = getScaleUnits();
Chris@411 287
Chris@411 288 if (m_verticalScale == LogScale) logarithmic = true;
Chris@411 289
Chris@411 290 return true;
Chris@411 291 }
Chris@411 292
Chris@411 293 bool
Chris@905 294 RegionLayer::getDisplayExtents(double &min, double &max) const
Chris@411 295 {
Chris@433 296 if (!m_model ||
Chris@433 297 m_verticalScale == AutoAlignScale ||
Chris@433 298 m_verticalScale == EqualSpaced) return false;
Chris@411 299
Chris@411 300 min = m_model->getValueMinimum();
Chris@411 301 max = m_model->getValueMaximum();
Chris@411 302
Chris@411 303 return true;
Chris@411 304 }
Chris@411 305
Chris@1428 306 EventVector
Chris@918 307 RegionLayer::getLocalPoints(LayerGeometryProvider *v, int x) const
Chris@411 308 {
Chris@1428 309 if (!m_model) return EventVector();
Chris@411 310
Chris@989 311 sv_frame_t frame = v->getFrameForX(x);
Chris@411 312
Chris@1428 313 EventVector local = m_model->getEventsCovering(frame);
Chris@1428 314 if (!local.empty()) return local;
Chris@411 315
Chris@1428 316 int fuzz = ViewManager::scalePixelSize(2);
Chris@1428 317 sv_frame_t start = v->getFrameForX(x - fuzz);
Chris@1428 318 sv_frame_t end = v->getFrameForX(x + fuzz);
Chris@411 319
Chris@1428 320 local = m_model->getEventsStartingWithin(frame, end - frame);
Chris@1428 321 if (!local.empty()) return local;
Chris@411 322
Chris@1428 323 local = m_model->getEventsSpanning(start, frame - start);
Chris@1428 324 if (!local.empty()) return local;
Chris@411 325
Chris@1428 326 return {};
Chris@411 327 }
Chris@411 328
Chris@550 329 bool
Chris@1428 330 RegionLayer::getPointToDrag(LayerGeometryProvider *v, int x, int y, Event &point) const
Chris@550 331 {
Chris@550 332 if (!m_model) return false;
Chris@550 333
Chris@989 334 sv_frame_t frame = v->getFrameForX(x);
Chris@550 335
Chris@1428 336 EventVector onPoints = m_model->getEventsCovering(frame);
Chris@550 337 if (onPoints.empty()) return false;
Chris@550 338
Chris@550 339 int nearestDistance = -1;
Chris@1428 340 for (const auto &p: onPoints) {
Chris@1428 341 int distance = getYForValue(v, p.getValue()) - y;
Chris@550 342 if (distance < 0) distance = -distance;
Chris@550 343 if (nearestDistance == -1 || distance < nearestDistance) {
Chris@550 344 nearestDistance = distance;
Chris@1428 345 point = p;
Chris@550 346 }
Chris@550 347 }
Chris@550 348
Chris@550 349 return true;
Chris@550 350 }
Chris@550 351
Chris@411 352 QString
Chris@909 353 RegionLayer::getLabelPreceding(sv_frame_t frame) const
Chris@552 354 {
Chris@552 355 if (!m_model) return "";
Chris@1428 356 EventVector points = m_model->getEventsStartingWithin
Chris@1428 357 (m_model->getStartFrame(), frame - m_model->getStartFrame());
Chris@1428 358 if (!points.empty()) {
Chris@1428 359 for (auto i = points.rbegin(); i != points.rend(); ++i) {
Chris@1428 360 if (i->getLabel() != QString()) {
Chris@1428 361 return i->getLabel();
Chris@1428 362 }
Chris@1428 363 }
Chris@552 364 }
Chris@1428 365 return QString();
Chris@552 366 }
Chris@552 367
Chris@552 368 QString
Chris@918 369 RegionLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &pos) const
Chris@411 370 {
Chris@411 371 int x = pos.x();
Chris@411 372
Chris@411 373 if (!m_model || !m_model->getSampleRate()) return "";
Chris@411 374
Chris@1428 375 EventVector points = getLocalPoints(v, x);
Chris@411 376
Chris@411 377 if (points.empty()) {
Chris@1266 378 if (!m_model->isReady()) {
Chris@1266 379 return tr("In progress");
Chris@1266 380 } else {
Chris@1266 381 return tr("No local points");
Chris@1266 382 }
Chris@411 383 }
Chris@411 384
Chris@1428 385 Event region;
Chris@1428 386 EventVector::iterator i;
Chris@411 387
Chris@413 388 //!!! harmonise with whatever decision is made about point y
Chris@413 389 //!!! coords in paint method
Chris@413 390
Chris@411 391 for (i = points.begin(); i != points.end(); ++i) {
Chris@411 392
Chris@1428 393 int y = getYForValue(v, i->getValue());
Chris@1266 394 int h = 3;
Chris@411 395
Chris@1266 396 if (m_model->getValueQuantization() != 0.0) {
Chris@1428 397 h = y - getYForValue
Chris@1428 398 (v, i->getValue() + m_model->getValueQuantization());
Chris@1266 399 if (h < 3) h = 3;
Chris@1266 400 }
Chris@411 401
Chris@1266 402 if (pos.y() >= y - h && pos.y() <= y) {
Chris@1266 403 region = *i;
Chris@1266 404 break;
Chris@1266 405 }
Chris@411 406 }
Chris@411 407
Chris@411 408 if (i == points.end()) return tr("No local points");
Chris@411 409
Chris@1428 410 RealTime rt = RealTime::frame2RealTime(region.getFrame(),
Chris@1266 411 m_model->getSampleRate());
Chris@1428 412 RealTime rd = RealTime::frame2RealTime(region.getDuration(),
Chris@1266 413 m_model->getSampleRate());
Chris@411 414
Chris@411 415 QString valueText;
Chris@411 416
Chris@1428 417 valueText = tr("%1 %2").arg(region.getValue()).arg(getScaleUnits());
Chris@411 418
Chris@411 419 QString text;
Chris@411 420
Chris@1428 421 if (region.getLabel() == "") {
Chris@1266 422 text = QString(tr("Time:\t%1\nValue:\t%2\nDuration:\t%3\nNo label"))
Chris@1266 423 .arg(rt.toText(true).c_str())
Chris@1266 424 .arg(valueText)
Chris@1266 425 .arg(rd.toText(true).c_str());
Chris@411 426 } else {
Chris@1266 427 text = QString(tr("Time:\t%1\nValue:\t%2\nDuration:\t%3\nLabel:\t%4"))
Chris@1266 428 .arg(rt.toText(true).c_str())
Chris@1266 429 .arg(valueText)
Chris@1266 430 .arg(rd.toText(true).c_str())
Chris@1428 431 .arg(region.getLabel());
Chris@411 432 }
Chris@411 433
Chris@1428 434 pos = QPoint(v->getXForFrame(region.getFrame()),
Chris@1428 435 getYForValue(v, region.getValue()));
Chris@411 436 return text;
Chris@411 437 }
Chris@411 438
Chris@411 439 bool
Chris@918 440 RegionLayer::snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
Chris@805 441 int &resolution,
Chris@414 442 SnapType snap) const
Chris@411 443 {
Chris@411 444 if (!m_model) {
Chris@1266 445 return Layer::snapToFeatureFrame(v, frame, resolution, snap);
Chris@411 446 }
Chris@411 447
Chris@1432 448 // SnapLeft / SnapRight: return frame of nearest feature in that
Chris@1432 449 // direction no matter how far away
Chris@1432 450 //
Chris@1432 451 // SnapNeighbouring: return frame of feature that would be used in
Chris@1432 452 // an editing operation, i.e. closest feature in either direction
Chris@1432 453 // but only if it is "close enough"
Chris@1432 454
Chris@411 455 resolution = m_model->getResolution();
Chris@411 456
Chris@411 457 if (snap == SnapNeighbouring) {
Chris@1432 458 EventVector points = getLocalPoints(v, v->getXForFrame(frame));
Chris@1266 459 if (points.empty()) return false;
Chris@1428 460 frame = points.begin()->getFrame();
Chris@1266 461 return true;
Chris@411 462 }
Chris@411 463
Chris@1432 464 // Normally we snap to the start frame of whichever event we
Chris@1432 465 // find. However here, for SnapRight only, if the end frame of
Chris@1432 466 // whichever event we would have snapped to had we been snapping
Chris@1432 467 // left is closer than the start frame of the next event to the
Chris@1432 468 // right, then we snap to that frame instead. Clear?
Chris@1428 469
Chris@1432 470 Event left;
Chris@1432 471 bool haveLeft = false;
Chris@1432 472 if (m_model->getNearestEventMatching
Chris@1432 473 (frame, [](Event) { return true; }, EventSeries::Backward, left)) {
Chris@1432 474 haveLeft = true;
Chris@1432 475 }
Chris@411 476
Chris@1432 477 if (snap == SnapLeft) {
Chris@1432 478 frame = left.getFrame();
Chris@1432 479 return haveLeft;
Chris@1432 480 }
Chris@411 481
Chris@1432 482 Event right;
Chris@1432 483 bool haveRight = false;
Chris@1432 484 if (m_model->getNearestEventMatching
Chris@1432 485 (frame, [](Event) { return true; }, EventSeries::Forward, right)) {
Chris@1432 486 haveRight = true;
Chris@1432 487 }
Chris@411 488
Chris@1432 489 if (haveLeft) {
Chris@1432 490 sv_frame_t leftEnd = left.getFrame() + left.getDuration();
Chris@1432 491 if (leftEnd > frame) {
Chris@1432 492 if (haveRight) {
Chris@1432 493 if (leftEnd - frame < right.getFrame() - frame) {
Chris@1432 494 frame = leftEnd;
Chris@1432 495 } else {
Chris@1432 496 frame = right.getFrame();
Chris@414 497 }
Chris@414 498 } else {
Chris@1432 499 frame = leftEnd;
Chris@414 500 }
Chris@1432 501 return true;
Chris@1266 502 }
Chris@411 503 }
Chris@411 504
Chris@1432 505 if (haveRight) {
Chris@1432 506 frame = right.getFrame();
Chris@1432 507 return true;
Chris@1432 508 }
Chris@1432 509
Chris@1432 510 return false;
Chris@411 511 }
Chris@411 512
Chris@559 513 bool
Chris@918 514 RegionLayer::snapToSimilarFeature(LayerGeometryProvider *v, sv_frame_t &frame,
Chris@805 515 int &resolution,
Chris@559 516 SnapType snap) const
Chris@559 517 {
Chris@559 518 if (!m_model) {
Chris@1266 519 return Layer::snapToSimilarFeature(v, frame, resolution, snap);
Chris@559 520 }
Chris@559 521
Chris@1432 522 // snap is only permitted to be SnapLeft or SnapRight here. We
Chris@1432 523 // don't do the same trick as in snapToFeatureFrame, of snapping
Chris@1432 524 // to the end of a feature sometimes.
Chris@1432 525
Chris@559 526 resolution = m_model->getResolution();
Chris@559 527
Chris@1432 528 Event ref;
Chris@1432 529 Event e;
Chris@1432 530 float matchvalue;
Chris@1432 531 bool found;
Chris@559 532
Chris@1432 533 found = m_model->getNearestEventMatching
Chris@1432 534 (frame, [](Event) { return true; }, EventSeries::Backward, ref);
Chris@559 535
Chris@1432 536 if (!found) {
Chris@1432 537 return false;
Chris@559 538 }
Chris@559 539
Chris@1432 540 matchvalue = ref.getValue();
Chris@1432 541
Chris@1432 542 found = m_model->getNearestEventMatching
Chris@1432 543 (frame,
Chris@1432 544 [matchvalue](Event e) {
Chris@1432 545 double epsilon = 0.0001;
Chris@1432 546 return fabs(e.getValue() - matchvalue) < epsilon;
Chris@1432 547 },
Chris@1432 548 snap == SnapLeft ? EventSeries::Backward : EventSeries::Forward,
Chris@1432 549 e);
Chris@559 550
Chris@1432 551 if (!found) {
Chris@1432 552 return false;
Chris@559 553 }
Chris@559 554
Chris@1432 555 frame = e.getFrame();
Chris@1432 556 return true;
Chris@559 557 }
Chris@559 558
Chris@701 559 QString
Chris@701 560 RegionLayer::getScaleUnits() const
Chris@701 561 {
Chris@701 562 if (m_model) return m_model->getScaleUnits();
Chris@701 563 else return "";
Chris@701 564 }
Chris@701 565
Chris@411 566 void
Chris@918 567 RegionLayer::getScaleExtents(LayerGeometryProvider *v, double &min, double &max, bool &log) const
Chris@411 568 {
Chris@411 569 min = 0.0;
Chris@411 570 max = 0.0;
Chris@411 571 log = false;
Chris@411 572
Chris@411 573 QString queryUnits;
Chris@701 574 queryUnits = getScaleUnits();
Chris@411 575
Chris@411 576 if (m_verticalScale == AutoAlignScale) {
Chris@411 577
Chris@411 578 if (!v->getValueExtents(queryUnits, min, max, log)) {
Chris@411 579
Chris@411 580 min = m_model->getValueMinimum();
Chris@411 581 max = m_model->getValueMaximum();
Chris@411 582
Chris@682 583 // cerr << "RegionLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << endl;
Chris@411 584
Chris@411 585 } else if (log) {
Chris@411 586
Chris@411 587 LogRange::mapRange(min, max);
Chris@411 588
Chris@682 589 // cerr << "RegionLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << endl;
Chris@411 590
Chris@411 591 }
Chris@411 592
Chris@433 593 } else if (m_verticalScale == EqualSpaced) {
Chris@433 594
Chris@433 595 if (!m_spacingMap.empty()) {
Chris@433 596 SpacingMap::const_iterator i = m_spacingMap.begin();
Chris@433 597 min = i->second;
Chris@433 598 i = m_spacingMap.end();
Chris@433 599 --i;
Chris@433 600 max = i->second;
Chris@682 601 // cerr << "RegionLayer[" << this << "]::getScaleExtents: equal spaced; min = " << min << ", max = " << max << ", log = " << log << endl;
Chris@433 602 }
Chris@433 603
Chris@411 604 } else {
Chris@411 605
Chris@411 606 min = m_model->getValueMinimum();
Chris@411 607 max = m_model->getValueMaximum();
Chris@411 608
Chris@411 609 if (m_verticalScale == LogScale) {
Chris@411 610 LogRange::mapRange(min, max);
Chris@411 611 log = true;
Chris@411 612 }
Chris@411 613 }
Chris@411 614
Chris@411 615 if (max == min) max = min + 1.0;
Chris@411 616 }
Chris@411 617
Chris@411 618 int
Chris@918 619 RegionLayer::spacingIndexToY(LayerGeometryProvider *v, int i) const
Chris@542 620 {
Chris@918 621 int h = v->getPaintHeight();
Chris@905 622 int n = int(m_spacingMap.size());
Chris@542 623 // this maps from i (spacing of the value from the spacing
Chris@542 624 // map) and n (number of region types) to y
Chris@542 625 int y = h - (((h * i) / n) + (h / (2 * n)));
Chris@542 626 return y;
Chris@542 627 }
Chris@542 628
Chris@905 629 double
Chris@918 630 RegionLayer::yToSpacingIndex(LayerGeometryProvider *v, int y) const
Chris@542 631 {
Chris@905 632 // we return an inexact result here (double rather than int)
Chris@918 633 int h = v->getPaintHeight();
Chris@905 634 int n = int(m_spacingMap.size());
Chris@551 635 // from y = h - ((h * i) / n) + (h / (2 * n)) as above (vh taking place of i)
Chris@905 636 double vh = double(2*h*n - h - 2*n*y) / double(2*h);
Chris@542 637 return vh;
Chris@542 638 }
Chris@542 639
Chris@542 640 int
Chris@918 641 RegionLayer::getYForValue(LayerGeometryProvider *v, double val) const
Chris@411 642 {
Chris@905 643 double min = 0.0, max = 0.0;
Chris@411 644 bool logarithmic = false;
Chris@918 645 int h = v->getPaintHeight();
Chris@411 646
Chris@433 647 if (m_verticalScale == EqualSpaced) {
Chris@433 648
Chris@433 649 if (m_spacingMap.empty()) return h/2;
Chris@433 650
Chris@433 651 SpacingMap::const_iterator i = m_spacingMap.lower_bound(val);
Chris@433 652 //!!! what now, if i->first != v?
Chris@433 653
Chris@542 654 int y = spacingIndexToY(v, i->second);
Chris@433 655
Chris@587 656 // SVDEBUG << "RegionLayer::getYForValue: value " << val << " -> i->second " << i->second << " -> y " << y << endl;
Chris@542 657 return y;
Chris@433 658
Chris@433 659
Chris@433 660 } else {
Chris@433 661
Chris@433 662 getScaleExtents(v, min, max, logarithmic);
Chris@411 663
Chris@682 664 // cerr << "RegionLayer[" << this << "]::getYForValue(" << val << "): min = " << min << ", max = " << max << ", log = " << logarithmic << endl;
Chris@682 665 // cerr << "h = " << h << ", margin = " << margin << endl;
Chris@411 666
Chris@433 667 if (logarithmic) {
Chris@433 668 val = LogRange::map(val);
Chris@433 669 }
Chris@433 670
Chris@433 671 return int(h - ((val - min) * h) / (max - min));
Chris@411 672 }
Chris@411 673 }
Chris@411 674
Chris@905 675 double
Chris@918 676 RegionLayer::getValueForY(LayerGeometryProvider *v, int y) const
Chris@701 677 {
Chris@701 678 return getValueForY(v, y, -1);
Chris@701 679 }
Chris@701 680
Chris@905 681 double
Chris@918 682 RegionLayer::getValueForY(LayerGeometryProvider *v, int y, int avoid) const
Chris@542 683 {
Chris@905 684 double min = 0.0, max = 0.0;
Chris@542 685 bool logarithmic = false;
Chris@918 686 int h = v->getPaintHeight();
Chris@542 687
Chris@542 688 if (m_verticalScale == EqualSpaced) {
Chris@542 689
Chris@542 690 // if we're equal spaced, we probably want to snap to the
Chris@542 691 // nearest item when close to it, and give some notification
Chris@542 692 // that we're doing so
Chris@542 693
Chris@542 694 if (m_spacingMap.empty()) return 1.f;
Chris@542 695
Chris@542 696 // n is the number of distinct regions. if we are close to
Chris@542 697 // one of the m/n divisions in the y scale, we should snap to
Chris@542 698 // the value of the mth region.
Chris@542 699
Chris@905 700 double vh = yToSpacingIndex(v, y);
Chris@542 701
Chris@542 702 // spacings in the map are integral, so find the closest one,
Chris@542 703 // map it back to its y coordinate, and see how far we are
Chris@542 704 // from it
Chris@542 705
Chris@905 706 int n = int(m_spacingMap.size());
Chris@905 707 int ivh = int(lrint(vh));
Chris@542 708 if (ivh < 0) ivh = 0;
Chris@542 709 if (ivh > n-1) ivh = n-1;
Chris@542 710 int iy = spacingIndexToY(v, ivh);
Chris@542 711
Chris@542 712 int dist = iy - y;
Chris@542 713 int gap = h / n; // between region lines
Chris@542 714
Chris@682 715 // cerr << "getValueForY: y = " << y << ", vh = " << vh << ", ivh = " << ivh << " of " << n << ", iy = " << iy << ", dist = " << dist << ", gap = " << gap << endl;
Chris@542 716
Chris@542 717 SpacingMap::const_iterator i = m_spacingMap.begin();
Chris@542 718 while (i != m_spacingMap.end()) {
Chris@542 719 if (i->second == ivh) break;
Chris@542 720 ++i;
Chris@542 721 }
Chris@542 722 if (i == m_spacingMap.end()) i = m_spacingMap.begin();
Chris@542 723
Chris@682 724 // cerr << "nearest existing value = " << i->first << " at " << iy << endl;
Chris@551 725
Chris@905 726 double val = 0;
Chris@542 727
Chris@682 728 // cerr << "note: avoid = " << avoid << ", i->second = " << i->second << endl;
Chris@551 729
Chris@551 730 if (dist < -gap/3 &&
Chris@551 731 ((avoid == -1) ||
Chris@551 732 (avoid != i->second && avoid != i->second - 1))) {
Chris@542 733 // bisect gap to prior
Chris@542 734 if (i == m_spacingMap.begin()) {
Chris@542 735 val = i->first - 1.f;
Chris@682 736 // cerr << "extended down to " << val << endl;
Chris@542 737 } else {
Chris@542 738 SpacingMap::const_iterator j = i;
Chris@542 739 --j;
Chris@542 740 val = (i->first + j->first) / 2;
Chris@682 741 // cerr << "bisected down to " << val << endl;
Chris@542 742 }
Chris@551 743 } else if (dist > gap/3 &&
Chris@551 744 ((avoid == -1) ||
Chris@551 745 (avoid != i->second && avoid != i->second + 1))) {
Chris@542 746 // bisect gap to following
Chris@542 747 SpacingMap::const_iterator j = i;
Chris@542 748 ++j;
Chris@542 749 if (j == m_spacingMap.end()) {
Chris@542 750 val = i->first + 1.f;
Chris@682 751 // cerr << "extended up to " << val << endl;
Chris@542 752 } else {
Chris@542 753 val = (i->first + j->first) / 2;
Chris@682 754 // cerr << "bisected up to " << val << endl;
Chris@542 755 }
Chris@551 756 } else {
Chris@551 757 // snap
Chris@551 758 val = i->first;
Chris@682 759 // cerr << "snapped to " << val << endl;
Chris@542 760 }
Chris@542 761
Chris@542 762 return val;
Chris@542 763
Chris@542 764 } else {
Chris@542 765
Chris@542 766 getScaleExtents(v, min, max, logarithmic);
Chris@542 767
Chris@905 768 double val = min + (double(h - y) * double(max - min)) / h;
Chris@542 769
Chris@542 770 if (logarithmic) {
Chris@905 771 val = pow(10.0, val);
Chris@542 772 }
Chris@542 773
Chris@542 774 return val;
Chris@542 775 }
Chris@542 776 }
Chris@542 777
Chris@427 778 QColor
Chris@918 779 RegionLayer::getColourForValue(LayerGeometryProvider *v, double val) const
Chris@427 780 {
Chris@905 781 double min, max;
Chris@427 782 bool log;
Chris@427 783 getScaleExtents(v, min, max, log);
Chris@427 784
Chris@427 785 if (min > max) std::swap(min, max);
Chris@427 786 if (max == min) max = min + 1;
Chris@427 787
Chris@427 788 if (log) {
Chris@427 789 LogRange::mapRange(min, max);
Chris@427 790 val = LogRange::map(val);
Chris@427 791 }
Chris@427 792
Chris@587 793 // SVDEBUG << "RegionLayer::getColourForValue: min " << min << ", max "
Chris@585 794 // << max << ", log " << log << ", value " << val << endl;
Chris@427 795
Chris@1362 796 QColor solid = ColourMapper(m_colourMap, m_colourInverted, min, max).map(val);
Chris@427 797 return QColor(solid.red(), solid.green(), solid.blue(), 120);
Chris@427 798 }
Chris@427 799
Chris@427 800 int
Chris@427 801 RegionLayer::getDefaultColourHint(bool darkbg, bool &impose)
Chris@427 802 {
Chris@427 803 impose = false;
Chris@427 804 return ColourDatabase::getInstance()->getColourIndex
Chris@427 805 (QString(darkbg ? "Bright Blue" : "Blue"));
Chris@427 806 }
Chris@427 807
Chris@411 808 void
Chris@916 809 RegionLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
Chris@411 810 {
Chris@411 811 if (!m_model || !m_model->isOK()) return;
Chris@411 812
Chris@905 813 sv_samplerate_t sampleRate = m_model->getSampleRate();
Chris@411 814 if (!sampleRate) return;
Chris@411 815
Chris@411 816 // Profiler profiler("RegionLayer::paint", true);
Chris@411 817
Chris@552 818 int x0 = rect.left() - 40, x1 = rect.right();
Chris@411 819
Chris@1409 820 sv_frame_t wholeFrame0 = v->getFrameForX(0);
Chris@1409 821 sv_frame_t wholeFrame1 = v->getFrameForX(v->getPaintWidth());
Chris@1409 822
Chris@1428 823 EventVector points(m_model->getEventsSpanning(wholeFrame0,
Chris@1428 824 wholeFrame1 - wholeFrame0));
Chris@411 825 if (points.empty()) return;
Chris@411 826
Chris@411 827 paint.setPen(getBaseQColor());
Chris@411 828
Chris@411 829 QColor brushColour(getBaseQColor());
Chris@411 830 brushColour.setAlpha(80);
Chris@411 831
Chris@587 832 // SVDEBUG << "RegionLayer::paint: resolution is "
Chris@1266 833 // << m_model->getResolution() << " frames" << endl;
Chris@411 834
Chris@905 835 double min = m_model->getValueMinimum();
Chris@905 836 double max = m_model->getValueMaximum();
Chris@411 837 if (max == min) max = min + 1.0;
Chris@411 838
Chris@411 839 QPoint localPos;
Chris@1428 840 Event illuminatePoint(0);
Chris@551 841 bool shouldIlluminate = false;
Chris@411 842
Chris@411 843 if (v->shouldIlluminateLocalFeatures(this, localPos)) {
Chris@551 844 shouldIlluminate = getPointToDrag(v, localPos.x(), localPos.y(),
Chris@551 845 illuminatePoint);
Chris@411 846 }
Chris@411 847
Chris@411 848 paint.save();
Chris@411 849 paint.setRenderHint(QPainter::Antialiasing, false);
Chris@411 850
Chris@413 851 //!!! point y coords if model does not haveDistinctValues() should
Chris@413 852 //!!! be assigned to avoid overlaps
Chris@413 853
Chris@413 854 //!!! if it does have distinct values, we should still ensure y
Chris@413 855 //!!! coord is never completely flat on the top or bottom
Chris@413 856
Chris@550 857 int fontHeight = paint.fontMetrics().height();
Chris@550 858
Chris@1428 859 for (EventVector::const_iterator i = points.begin();
Chris@1266 860 i != points.end(); ++i) {
Chris@411 861
Chris@1428 862 const Event &p(*i);
Chris@411 863
Chris@1428 864 int x = v->getXForFrame(p.getFrame());
Chris@1428 865 int w = v->getXForFrame(p.getFrame() + p.getDuration()) - x;
Chris@1428 866 int y = getYForValue(v, p.getValue());
Chris@1266 867 int h = 9;
Chris@1266 868 int ex = x + w;
Chris@427 869
Chris@1409 870 int gap = v->scalePixelSize(2);
Chris@1409 871
Chris@1428 872 EventVector::const_iterator j = i;
Chris@1266 873 ++j;
Chris@427 874
Chris@1266 875 if (j != points.end()) {
Chris@1428 876 const Event &q(*j);
Chris@1428 877 int nx = v->getXForFrame(q.getFrame());
Chris@433 878 if (nx < ex) ex = nx;
Chris@427 879 }
Chris@427 880
Chris@1266 881 if (m_model->getValueQuantization() != 0.0) {
Chris@1428 882 h = y - getYForValue
Chris@1428 883 (v, p.getValue() + m_model->getValueQuantization());
Chris@1266 884 if (h < 3) h = 3;
Chris@1266 885 }
Chris@411 886
Chris@1266 887 if (w < 1) w = 1;
Chris@411 888
Chris@1266 889 if (m_plotStyle == PlotSegmentation) {
Chris@918 890 paint.setPen(getForegroundQColor(v->getView()));
Chris@1428 891 paint.setBrush(getColourForValue(v, p.getValue()));
Chris@427 892 } else {
Chris@427 893 paint.setPen(getBaseQColor());
Chris@427 894 paint.setBrush(brushColour);
Chris@427 895 }
Chris@427 896
Chris@1266 897 if (m_plotStyle == PlotSegmentation) {
Chris@427 898
Chris@1266 899 if (ex <= x) continue;
Chris@427 900
Chris@1428 901 if (!shouldIlluminate || illuminatePoint != p) {
Chris@551 902
Chris@918 903 paint.setPen(QPen(getForegroundQColor(v->getView()), 1));
Chris@918 904 paint.drawLine(x, 0, x, v->getPaintHeight());
Chris@551 905 paint.setPen(Qt::NoPen);
Chris@552 906
Chris@552 907 } else {
Chris@918 908 paint.setPen(QPen(getForegroundQColor(v->getView()), 2));
Chris@551 909 }
Chris@427 910
Chris@1409 911 paint.drawRect(x, -1, ex - x, v->getPaintHeight() + gap);
Chris@427 912
Chris@1266 913 } else {
Chris@427 914
Chris@1428 915 if (shouldIlluminate && illuminatePoint == p) {
Chris@551 916
Chris@551 917 paint.setPen(v->getForeground());
Chris@551 918 paint.setBrush(v->getForeground());
Chris@551 919
Chris@1428 920 QString vlabel =
Chris@1428 921 QString("%1%2").arg(p.getValue()).arg(getScaleUnits());
Chris@1078 922 PaintAssistant::drawVisibleText(v, paint,
Chris@1409 923 x - paint.fontMetrics().width(vlabel) - gap,
Chris@551 924 y + paint.fontMetrics().height()/2
Chris@551 925 - paint.fontMetrics().descent(),
Chris@1078 926 vlabel, PaintAssistant::OutlinedText);
Chris@551 927
Chris@551 928 QString hlabel = RealTime::frame2RealTime
Chris@1428 929 (p.getFrame(), m_model->getSampleRate()).toText(true).c_str();
Chris@1078 930 PaintAssistant::drawVisibleText(v, paint,
Chris@551 931 x,
Chris@1409 932 y - h/2 - paint.fontMetrics().descent() - gap,
Chris@1078 933 hlabel, PaintAssistant::OutlinedText);
Chris@427 934 }
Chris@551 935
Chris@427 936 paint.drawLine(x, y-1, x + w, y-1);
Chris@427 937 paint.drawLine(x, y+1, x + w, y+1);
Chris@427 938 paint.drawLine(x, y - h/2, x, y + h/2);
Chris@427 939 paint.drawLine(x+w, y - h/2, x + w, y + h/2);
Chris@427 940 }
Chris@552 941 }
Chris@552 942
Chris@552 943 int nextLabelMinX = -100;
Chris@552 944 int lastLabelY = 0;
Chris@552 945
Chris@1428 946 for (EventVector::const_iterator i = points.begin();
Chris@1266 947 i != points.end(); ++i) {
Chris@552 948
Chris@1428 949 const Event &p(*i);
Chris@552 950
Chris@1428 951 int x = v->getXForFrame(p.getFrame());
Chris@1428 952 int w = v->getXForFrame(p.getFrame() + p.getDuration()) - x;
Chris@1428 953 int y = getYForValue(v, p.getValue());
Chris@552 954
Chris@1428 955 QString label = p.getLabel();
Chris@1409 956 if (label == "") {
Chris@1428 957 label = QString("%1%2").arg(p.getValue()).arg(getScaleUnits());
Chris@1409 958 }
Chris@1409 959 int labelWidth = paint.fontMetrics().width(label);
Chris@1409 960
Chris@1409 961 int gap = v->scalePixelSize(2);
Chris@1409 962
Chris@1409 963 if (m_plotStyle == PlotSegmentation) {
Chris@1409 964 if ((x + w < x0 && x + labelWidth + gap < x0) || x > x1) {
Chris@1409 965 continue;
Chris@1409 966 }
Chris@1409 967 } else {
Chris@1409 968 if (x + w < x0 || x - labelWidth - gap > x1) {
Chris@1409 969 continue;
Chris@1409 970 }
Chris@1409 971 }
Chris@1409 972
Chris@552 973 bool illuminated = false;
Chris@552 974
Chris@1266 975 if (m_plotStyle != PlotSegmentation) {
Chris@1428 976 if (shouldIlluminate && illuminatePoint == p) {
Chris@552 977 illuminated = true;
Chris@552 978 }
Chris@552 979 }
Chris@427 980
Chris@551 981 if (!illuminated) {
Chris@551 982
Chris@552 983 int labelX, labelY;
Chris@552 984
Chris@551 985 if (m_plotStyle != PlotSegmentation) {
Chris@1409 986 labelX = x - labelWidth - gap;
Chris@552 987 labelY = y + paint.fontMetrics().height()/2
Chris@552 988 - paint.fontMetrics().descent();
Chris@551 989 } else {
Chris@552 990 labelX = x + 5;
Chris@552 991 labelY = v->getTextLabelHeight(this, paint);
Chris@552 992 if (labelX < nextLabelMinX) {
Chris@918 993 if (lastLabelY < v->getPaintHeight()/2) {
Chris@552 994 labelY = lastLabelY + fontHeight;
Chris@552 995 }
Chris@552 996 }
Chris@552 997 lastLabelY = labelY;
Chris@1409 998 nextLabelMinX = labelX + labelWidth;
Chris@551 999 }
Chris@552 1000
Chris@1409 1001 PaintAssistant::drawVisibleText(v, paint, labelX, labelY, label,
Chris@1409 1002 PaintAssistant::OutlinedText);
Chris@550 1003 }
Chris@411 1004 }
Chris@411 1005
Chris@411 1006 paint.restore();
Chris@411 1007 }
Chris@411 1008
Chris@701 1009 int
Chris@918 1010 RegionLayer::getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &paint) const
Chris@701 1011 {
Chris@701 1012 if (!m_model ||
Chris@701 1013 m_verticalScale == AutoAlignScale ||
Chris@701 1014 m_verticalScale == EqualSpaced) {
Chris@701 1015 return 0;
Chris@701 1016 } else if (m_plotStyle == PlotSegmentation) {
Chris@701 1017 if (m_verticalScale == LogScale) {
Chris@701 1018 return LogColourScale().getWidth(v, paint);
Chris@701 1019 } else {
Chris@701 1020 return LinearColourScale().getWidth(v, paint);
Chris@701 1021 }
Chris@701 1022 } else {
Chris@701 1023 if (m_verticalScale == LogScale) {
Chris@701 1024 return LogNumericalScale().getWidth(v, paint);
Chris@701 1025 } else {
Chris@701 1026 return LinearNumericalScale().getWidth(v, paint);
Chris@701 1027 }
Chris@701 1028 }
Chris@701 1029 }
Chris@701 1030
Chris@701 1031 void
Chris@918 1032 RegionLayer::paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect) const
Chris@701 1033 {
Chris@1428 1034 if (!m_model || m_model->isEmpty()) return;
Chris@701 1035
Chris@701 1036 QString unit;
Chris@905 1037 double min, max;
Chris@701 1038 bool logarithmic;
Chris@701 1039
Chris@701 1040 int w = getVerticalScaleWidth(v, false, paint);
Chris@701 1041
Chris@701 1042 if (m_plotStyle == PlotSegmentation) {
Chris@701 1043
Chris@701 1044 getValueExtents(min, max, logarithmic, unit);
Chris@701 1045
Chris@701 1046 if (logarithmic) {
Chris@701 1047 LogRange::mapRange(min, max);
Chris@701 1048 LogColourScale().paintVertical(v, this, paint, 0, min, max);
Chris@701 1049 } else {
Chris@701 1050 LinearColourScale().paintVertical(v, this, paint, 0, min, max);
Chris@701 1051 }
Chris@701 1052
Chris@701 1053 } else {
Chris@701 1054
Chris@701 1055 getScaleExtents(v, min, max, logarithmic);
Chris@701 1056
Chris@701 1057 if (logarithmic) {
Chris@701 1058 LogNumericalScale().paintVertical(v, this, paint, 0, min, max);
Chris@701 1059 } else {
Chris@701 1060 LinearNumericalScale().paintVertical(v, this, paint, 0, min, max);
Chris@701 1061 }
Chris@701 1062 }
Chris@701 1063
Chris@701 1064 if (getScaleUnits() != "") {
Chris@701 1065 int mw = w - 5;
Chris@701 1066 paint.drawText(5,
Chris@701 1067 5 + paint.fontMetrics().ascent(),
Chris@701 1068 TextAbbrev::abbreviate(getScaleUnits(),
Chris@701 1069 paint.fontMetrics(),
Chris@701 1070 mw));
Chris@701 1071 }
Chris@701 1072 }
Chris@701 1073
Chris@411 1074 void
Chris@918 1075 RegionLayer::drawStart(LayerGeometryProvider *v, QMouseEvent *e)
Chris@411 1076 {
Chris@411 1077 if (!m_model) return;
Chris@411 1078
Chris@989 1079 sv_frame_t frame = v->getFrameForX(e->x());
Chris@411 1080 if (frame < 0) frame = 0;
Chris@411 1081 frame = frame / m_model->getResolution() * m_model->getResolution();
Chris@411 1082
Chris@905 1083 double value = getValueForY(v, e->y());
Chris@411 1084
Chris@1428 1085 m_editingPoint = Event(frame, float(value), 0, "");
Chris@411 1086 m_originalPoint = m_editingPoint;
Chris@411 1087
Chris@411 1088 if (m_editingCommand) finish(m_editingCommand);
Chris@1428 1089 m_editingCommand = new ChangeEventsCommand(m_model,
Chris@543 1090 tr("Draw Region"));
Chris@1428 1091 m_editingCommand->add(m_editingPoint);
Chris@411 1092
Chris@550 1093 recalcSpacing();
Chris@550 1094
Chris@411 1095 m_editing = true;
Chris@411 1096 }
Chris@411 1097
Chris@411 1098 void
Chris@918 1099 RegionLayer::drawDrag(LayerGeometryProvider *v, QMouseEvent *e)
Chris@411 1100 {
Chris@411 1101 if (!m_model || !m_editing) return;
Chris@411 1102
Chris@905 1103 sv_frame_t frame = v->getFrameForX(e->x());
Chris@411 1104 if (frame < 0) frame = 0;
Chris@411 1105 frame = frame / m_model->getResolution() * m_model->getResolution();
Chris@411 1106
Chris@1428 1107 double newValue = m_editingPoint.getValue();
Chris@542 1108 if (m_verticalScale != EqualSpaced) newValue = getValueForY(v, e->y());
Chris@411 1109
Chris@1428 1110 sv_frame_t newFrame = m_editingPoint.getFrame();
Chris@905 1111 sv_frame_t newDuration = frame - newFrame;
Chris@411 1112 if (newDuration < 0) {
Chris@411 1113 newFrame = frame;
Chris@411 1114 newDuration = -newDuration;
Chris@411 1115 } else if (newDuration == 0) {
Chris@411 1116 newDuration = 1;
Chris@411 1117 }
Chris@411 1118
Chris@1428 1119 m_editingCommand->remove(m_editingPoint);
Chris@1428 1120 m_editingPoint = m_editingPoint
Chris@1428 1121 .withFrame(newFrame)
Chris@1428 1122 .withValue(float(newValue))
Chris@1428 1123 .withDuration(newDuration);
Chris@1428 1124 m_editingCommand->add(m_editingPoint);
Chris@550 1125
Chris@551 1126 recalcSpacing();
Chris@411 1127 }
Chris@411 1128
Chris@411 1129 void
Chris@918 1130 RegionLayer::drawEnd(LayerGeometryProvider *, QMouseEvent *)
Chris@411 1131 {
Chris@411 1132 if (!m_model || !m_editing) return;
Chris@411 1133 finish(m_editingCommand);
Chris@1408 1134 m_editingCommand = nullptr;
Chris@411 1135 m_editing = false;
Chris@550 1136
Chris@550 1137 recalcSpacing();
Chris@411 1138 }
Chris@411 1139
Chris@411 1140 void
Chris@918 1141 RegionLayer::eraseStart(LayerGeometryProvider *v, QMouseEvent *e)
Chris@411 1142 {
Chris@411 1143 if (!m_model) return;
Chris@411 1144
Chris@550 1145 if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return;
Chris@411 1146
Chris@411 1147 if (m_editingCommand) {
Chris@1266 1148 finish(m_editingCommand);
Chris@1408 1149 m_editingCommand = nullptr;
Chris@411 1150 }
Chris@411 1151
Chris@411 1152 m_editing = true;
Chris@550 1153 recalcSpacing();
Chris@411 1154 }
Chris@411 1155
Chris@411 1156 void
Chris@918 1157 RegionLayer::eraseDrag(LayerGeometryProvider *, QMouseEvent *)
Chris@411 1158 {
Chris@411 1159 }
Chris@411 1160
Chris@411 1161 void
Chris@918 1162 RegionLayer::eraseEnd(LayerGeometryProvider *v, QMouseEvent *e)
Chris@411 1163 {
Chris@411 1164 if (!m_model || !m_editing) return;
Chris@411 1165
Chris@411 1166 m_editing = false;
Chris@551 1167
Chris@1428 1168 Event p(0);
Chris@550 1169 if (!getPointToDrag(v, e->x(), e->y(), p)) return;
Chris@1428 1170 if (p.getFrame() != m_editingPoint.getFrame() ||
Chris@1428 1171 p.getValue() != m_editingPoint.getValue()) return;
Chris@411 1172
Chris@1428 1173 m_editingCommand = new ChangeEventsCommand
Chris@543 1174 (m_model, tr("Erase Region"));
Chris@411 1175
Chris@1428 1176 m_editingCommand->remove(m_editingPoint);
Chris@411 1177
Chris@411 1178 finish(m_editingCommand);
Chris@1408 1179 m_editingCommand = nullptr;
Chris@411 1180 m_editing = false;
Chris@550 1181 recalcSpacing();
Chris@411 1182 }
Chris@411 1183
Chris@411 1184 void
Chris@918 1185 RegionLayer::editStart(LayerGeometryProvider *v, QMouseEvent *e)
Chris@411 1186 {
Chris@411 1187 if (!m_model) return;
Chris@411 1188
Chris@550 1189 if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) {
Chris@550 1190 return;
Chris@550 1191 }
Chris@550 1192
Chris@1428 1193 m_dragPointX = v->getXForFrame(m_editingPoint.getFrame());
Chris@1428 1194 m_dragPointY = getYForValue(v, m_editingPoint.getValue());
Chris@551 1195
Chris@411 1196 m_originalPoint = m_editingPoint;
Chris@411 1197
Chris@411 1198 if (m_editingCommand) {
Chris@1266 1199 finish(m_editingCommand);
Chris@1408 1200 m_editingCommand = nullptr;
Chris@411 1201 }
Chris@411 1202
Chris@411 1203 m_editing = true;
Chris@551 1204 m_dragStartX = e->x();
Chris@551 1205 m_dragStartY = e->y();
Chris@550 1206 recalcSpacing();
Chris@411 1207 }
Chris@411 1208
Chris@411 1209 void
Chris@918 1210 RegionLayer::editDrag(LayerGeometryProvider *v, QMouseEvent *e)
Chris@411 1211 {
Chris@411 1212 if (!m_model || !m_editing) return;
Chris@411 1213
Chris@551 1214 int xdist = e->x() - m_dragStartX;
Chris@551 1215 int ydist = e->y() - m_dragStartY;
Chris@551 1216 int newx = m_dragPointX + xdist;
Chris@551 1217 int newy = m_dragPointY + ydist;
Chris@551 1218
Chris@989 1219 sv_frame_t frame = v->getFrameForX(newx);
Chris@411 1220 if (frame < 0) frame = 0;
Chris@411 1221 frame = frame / m_model->getResolution() * m_model->getResolution();
Chris@411 1222
Chris@551 1223 // Do not bisect between two values, if one of those values is
Chris@551 1224 // that of the point we're actually moving ...
Chris@1428 1225 int avoid = m_spacingMap[m_editingPoint.getValue()];
Chris@551 1226
Chris@551 1227 // ... unless there are other points with the same value
Chris@1428 1228 if (m_distributionMap[m_editingPoint.getValue()] > 1) avoid = -1;
Chris@551 1229
Chris@905 1230 double value = getValueForY(v, newy, avoid);
Chris@411 1231
Chris@411 1232 if (!m_editingCommand) {
Chris@1428 1233 m_editingCommand = new ChangeEventsCommand(m_model,
Chris@1266 1234 tr("Drag Region"));
Chris@411 1235 }
Chris@411 1236
Chris@1428 1237 m_editingCommand->remove(m_editingPoint);
Chris@1428 1238 m_editingPoint = m_editingPoint
Chris@1428 1239 .withFrame(frame)
Chris@1428 1240 .withValue(float(value));
Chris@1428 1241 m_editingCommand->add(m_editingPoint);
Chris@550 1242 recalcSpacing();
Chris@411 1243 }
Chris@411 1244
Chris@411 1245 void
Chris@918 1246 RegionLayer::editEnd(LayerGeometryProvider *, QMouseEvent *)
Chris@411 1247 {
Chris@411 1248 if (!m_model || !m_editing) return;
Chris@411 1249
Chris@411 1250 if (m_editingCommand) {
Chris@411 1251
Chris@1266 1252 QString newName = m_editingCommand->getName();
Chris@411 1253
Chris@1428 1254 if (m_editingPoint.getFrame() != m_originalPoint.getFrame()) {
Chris@1428 1255 if (m_editingPoint.getValue() != m_originalPoint.getValue()) {
Chris@1266 1256 newName = tr("Edit Region");
Chris@1266 1257 } else {
Chris@1266 1258 newName = tr("Relocate Region");
Chris@1266 1259 }
Chris@1266 1260 } else {
Chris@1266 1261 newName = tr("Change Point Value");
Chris@1266 1262 }
Chris@411 1263
Chris@1266 1264 m_editingCommand->setName(newName);
Chris@1266 1265 finish(m_editingCommand);
Chris@411 1266 }
Chris@411 1267
Chris@1408 1268 m_editingCommand = nullptr;
Chris@411 1269 m_editing = false;
Chris@550 1270 recalcSpacing();
Chris@411 1271 }
Chris@411 1272
Chris@411 1273 bool
Chris@918 1274 RegionLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e)
Chris@411 1275 {
Chris@411 1276 if (!m_model) return false;
Chris@411 1277
Chris@1428 1278 Event region(0);
Chris@550 1279 if (!getPointToDrag(v, e->x(), e->y(), region)) return false;
Chris@550 1280
Chris@411 1281 ItemEditDialog *dialog = new ItemEditDialog
Chris@411 1282 (m_model->getSampleRate(),
Chris@411 1283 ItemEditDialog::ShowTime |
Chris@411 1284 ItemEditDialog::ShowDuration |
Chris@411 1285 ItemEditDialog::ShowValue |
Chris@411 1286 ItemEditDialog::ShowText,
Chris@701 1287 getScaleUnits());
Chris@411 1288
Chris@1428 1289 dialog->setFrameTime(region.getFrame());
Chris@1428 1290 dialog->setValue(region.getValue());
Chris@1428 1291 dialog->setFrameDuration(region.getDuration());
Chris@1428 1292 dialog->setText(region.getLabel());
Chris@411 1293
Chris@411 1294 if (dialog->exec() == QDialog::Accepted) {
Chris@411 1295
Chris@1428 1296 Event newRegion = region
Chris@1428 1297 .withFrame(dialog->getFrameTime())
Chris@1428 1298 .withValue(dialog->getValue())
Chris@1428 1299 .withDuration(dialog->getFrameDuration())
Chris@1428 1300 .withLabel(dialog->getText());
Chris@411 1301
Chris@1428 1302 ChangeEventsCommand *command = new ChangeEventsCommand
Chris@543 1303 (m_model, tr("Edit Region"));
Chris@1428 1304 command->remove(region);
Chris@1428 1305 command->add(newRegion);
Chris@411 1306 finish(command);
Chris@411 1307 }
Chris@411 1308
Chris@411 1309 delete dialog;
Chris@550 1310 recalcSpacing();
Chris@411 1311 return true;
Chris@411 1312 }
Chris@411 1313
Chris@411 1314 void
Chris@905 1315 RegionLayer::moveSelection(Selection s, sv_frame_t newStartFrame)
Chris@411 1316 {
Chris@411 1317 if (!m_model) return;
Chris@411 1318
Chris@1428 1319 ChangeEventsCommand *command =
Chris@1428 1320 new ChangeEventsCommand(m_model, tr("Drag Selection"));
Chris@411 1321
Chris@1428 1322 EventVector points =
Chris@1428 1323 m_model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
Chris@411 1324
Chris@1428 1325 for (EventVector::iterator i = points.begin();
Chris@1266 1326 i != points.end(); ++i) {
Chris@411 1327
Chris@1429 1328 Event newPoint = (*i)
Chris@1429 1329 .withFrame(i->getFrame() + newStartFrame - s.getStartFrame());
Chris@1429 1330 command->remove(*i);
Chris@1429 1331 command->add(newPoint);
Chris@411 1332 }
Chris@411 1333
Chris@411 1334 finish(command);
Chris@550 1335 recalcSpacing();
Chris@411 1336 }
Chris@411 1337
Chris@411 1338 void
Chris@411 1339 RegionLayer::resizeSelection(Selection s, Selection newSize)
Chris@411 1340 {
Chris@1428 1341 if (!m_model || !s.getDuration()) return;
Chris@411 1342
Chris@1428 1343 ChangeEventsCommand *command =
Chris@1428 1344 new ChangeEventsCommand(m_model, tr("Resize Selection"));
Chris@411 1345
Chris@1428 1346 EventVector points =
Chris@1428 1347 m_model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
Chris@411 1348
Chris@1428 1349 double ratio = double(newSize.getDuration()) / double(s.getDuration());
Chris@1428 1350 double oldStart = double(s.getStartFrame());
Chris@1428 1351 double newStart = double(newSize.getStartFrame());
Chris@1428 1352
Chris@1428 1353 for (Event p: points) {
Chris@411 1354
Chris@1428 1355 double newFrame = (double(p.getFrame()) - oldStart) * ratio + newStart;
Chris@1428 1356 double newDuration = double(p.getDuration()) * ratio;
Chris@411 1357
Chris@1428 1358 Event newPoint = p
Chris@1428 1359 .withFrame(lrint(newFrame))
Chris@1428 1360 .withDuration(lrint(newDuration));
Chris@1428 1361 command->remove(p);
Chris@1428 1362 command->add(newPoint);
Chris@411 1363 }
Chris@411 1364
Chris@411 1365 finish(command);
Chris@550 1366 recalcSpacing();
Chris@411 1367 }
Chris@411 1368
Chris@411 1369 void
Chris@411 1370 RegionLayer::deleteSelection(Selection s)
Chris@411 1371 {
Chris@411 1372 if (!m_model) return;
Chris@411 1373
Chris@1428 1374 ChangeEventsCommand *command =
Chris@1428 1375 new ChangeEventsCommand(m_model, tr("Delete Selected Points"));
Chris@411 1376
Chris@1428 1377 EventVector points =
Chris@1428 1378 m_model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
Chris@411 1379
Chris@1428 1380 for (EventVector::iterator i = points.begin();
Chris@1266 1381 i != points.end(); ++i) {
Chris@411 1382
Chris@1428 1383 if (s.contains(i->getFrame())) {
Chris@1428 1384 command->remove(*i);
Chris@411 1385 }
Chris@411 1386 }
Chris@411 1387
Chris@411 1388 finish(command);
Chris@550 1389 recalcSpacing();
Chris@411 1390 }
Chris@411 1391
Chris@411 1392 void
Chris@918 1393 RegionLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to)
Chris@411 1394 {
Chris@411 1395 if (!m_model) return;
Chris@411 1396
Chris@1428 1397 EventVector points =
Chris@1428 1398 m_model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
Chris@411 1399
Chris@1428 1400 for (Event p: points) {
Chris@1428 1401 to.addPoint(p.withReferenceFrame(alignToReference(v, p.getFrame())));
Chris@411 1402 }
Chris@411 1403 }
Chris@411 1404
Chris@411 1405 bool
Chris@918 1406 RegionLayer::paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t /* frameOffset */, bool /* interactive */)
Chris@411 1407 {
Chris@411 1408 if (!m_model) return false;
Chris@411 1409
Chris@1423 1410 const EventVector &points = from.getPoints();
Chris@411 1411
Chris@411 1412 bool realign = false;
Chris@411 1413
Chris@411 1414 if (clipboardHasDifferentAlignment(v, from)) {
Chris@411 1415
Chris@411 1416 QMessageBox::StandardButton button =
Chris@918 1417 QMessageBox::question(v->getView(), tr("Re-align pasted items?"),
Chris@411 1418 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@411 1419 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
Chris@411 1420 QMessageBox::Yes);
Chris@411 1421
Chris@411 1422 if (button == QMessageBox::Cancel) {
Chris@411 1423 return false;
Chris@411 1424 }
Chris@411 1425
Chris@411 1426 if (button == QMessageBox::Yes) {
Chris@411 1427 realign = true;
Chris@411 1428 }
Chris@411 1429 }
Chris@411 1430
Chris@1428 1431 ChangeEventsCommand *command =
Chris@1428 1432 new ChangeEventsCommand(m_model, tr("Paste"));
Chris@411 1433
Chris@1423 1434 for (EventVector::const_iterator i = points.begin();
Chris@411 1435 i != points.end(); ++i) {
Chris@411 1436
Chris@905 1437 sv_frame_t frame = 0;
Chris@411 1438
Chris@411 1439 if (!realign) {
Chris@411 1440
Chris@411 1441 frame = i->getFrame();
Chris@411 1442
Chris@411 1443 } else {
Chris@411 1444
Chris@1423 1445 if (i->hasReferenceFrame()) {
Chris@411 1446 frame = i->getReferenceFrame();
Chris@411 1447 frame = alignFromReference(v, frame);
Chris@411 1448 } else {
Chris@411 1449 frame = i->getFrame();
Chris@411 1450 }
Chris@411 1451 }
Chris@411 1452
Chris@1428 1453 Event p = *i;
Chris@1428 1454 Event newPoint = p;
Chris@1428 1455 if (!p.hasValue()) {
Chris@1428 1456 newPoint = newPoint.withValue((m_model->getValueMinimum() +
Chris@1428 1457 m_model->getValueMaximum()) / 2);
Chris@1428 1458 }
Chris@1428 1459 if (!p.hasDuration()) {
Chris@905 1460 sv_frame_t nextFrame = frame;
Chris@1423 1461 EventVector::const_iterator j = i;
Chris@411 1462 for (; j != points.end(); ++j) {
Chris@411 1463 if (j != i) break;
Chris@411 1464 }
Chris@411 1465 if (j != points.end()) {
Chris@411 1466 nextFrame = j->getFrame();
Chris@411 1467 }
Chris@411 1468 if (nextFrame == frame) {
Chris@1428 1469 newPoint = newPoint.withDuration(m_model->getResolution());
Chris@411 1470 } else {
Chris@1428 1471 newPoint = newPoint.withDuration(nextFrame - frame);
Chris@411 1472 }
Chris@411 1473 }
Chris@411 1474
Chris@1428 1475 command->add(newPoint);
Chris@411 1476 }
Chris@411 1477
Chris@411 1478 finish(command);
Chris@550 1479 recalcSpacing();
Chris@411 1480 return true;
Chris@411 1481 }
Chris@411 1482
Chris@411 1483 void
Chris@411 1484 RegionLayer::toXml(QTextStream &stream,
Chris@411 1485 QString indent, QString extraAttributes) const
Chris@411 1486 {
Chris@1362 1487 QString s;
Chris@1362 1488
Chris@1362 1489 s += QString("verticalScale=\"%1\" "
Chris@1362 1490 "plotStyle=\"%2\" ")
Chris@1362 1491 .arg(m_verticalScale)
Chris@1362 1492 .arg(m_plotStyle);
Chris@1362 1493
Chris@1362 1494 // New-style colour map attribute, by string id rather than by
Chris@1362 1495 // number
Chris@1362 1496
Chris@1362 1497 s += QString("fillColourMap=\"%1\" ")
Chris@1362 1498 .arg(ColourMapper::getColourMapId(m_colourMap));
Chris@1362 1499
Chris@1362 1500 // Old-style colour map attribute
Chris@1362 1501
Chris@1362 1502 s += QString("colourMap=\"%1\" ")
Chris@1362 1503 .arg(ColourMapper::getBackwardCompatibilityColourMap(m_colourMap));
Chris@1362 1504
Chris@1362 1505 SingleColourLayer::toXml(stream, indent, extraAttributes + " " + s);
Chris@411 1506 }
Chris@411 1507
Chris@411 1508 void
Chris@411 1509 RegionLayer::setProperties(const QXmlAttributes &attributes)
Chris@411 1510 {
Chris@411 1511 SingleColourLayer::setProperties(attributes);
Chris@411 1512
Chris@411 1513 bool ok;
Chris@411 1514 VerticalScale scale = (VerticalScale)
Chris@1266 1515 attributes.value("verticalScale").toInt(&ok);
Chris@411 1516 if (ok) setVerticalScale(scale);
Chris@412 1517 PlotStyle style = (PlotStyle)
Chris@1266 1518 attributes.value("plotStyle").toInt(&ok);
Chris@412 1519 if (ok) setPlotStyle(style);
Chris@1362 1520
Chris@1362 1521 QString colourMapId = attributes.value("fillColourMap");
Chris@1362 1522 int colourMap = ColourMapper::getColourMapById(colourMapId);
Chris@1362 1523 if (colourMap >= 0) {
Chris@1362 1524 setFillColourMap(colourMap);
Chris@1362 1525 } else {
Chris@1362 1526 colourMap = attributes.value("colourMap").toInt(&ok);
Chris@1362 1527 if (ok && colourMap < ColourMapper::getColourMapCount()) {
Chris@1362 1528 setFillColourMap(colourMap);
Chris@1362 1529 }
Chris@1362 1530 }
Chris@411 1531 }
Chris@411 1532
Chris@411 1533