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

Add option to make pane sizes auto-resize-only (i.e. remove user control via a splitter); also place alignment views above panes instead of below, meaning the extra bit of space that we currently have for the pane without one at least goes to the primary pane
author Chris Cannam
date Tue, 30 Apr 2019 15:53:21 +0100
parents 5b9692768beb
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