Mercurial > hg > svgui
comparison layer/TimeFrequencyBoxLayer.cpp @ 1511:a473b73fb045 time-frequency-boxes
Introduce time-frequency box layer
| author | Chris Cannam |
|---|---|
| date | Thu, 19 Sep 2019 15:18:28 +0100 |
| parents | |
| children | e453053a44dc |
comparison
equal
deleted
inserted
replaced
| 1510:872873aa6463 | 1511:a473b73fb045 |
|---|---|
| 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ | |
| 2 | |
| 3 /* | |
| 4 Sonic Visualiser | |
| 5 An audio file viewer and annotation editor. | |
| 6 Centre for Digital Music, Queen Mary, University of London. | |
| 7 | |
| 8 This program is free software; you can redistribute it and/or | |
| 9 modify it under the terms of the GNU General Public License as | |
| 10 published by the Free Software Foundation; either version 2 of the | |
| 11 License, or (at your option) any later version. See the file | |
| 12 COPYING included with this distribution for more information. | |
| 13 */ | |
| 14 | |
| 15 #include "TimeFrequencyBoxLayer.h" | |
| 16 | |
| 17 #include "data/model/Model.h" | |
| 18 #include "base/RealTime.h" | |
| 19 #include "base/Profiler.h" | |
| 20 #include "base/LogRange.h" | |
| 21 | |
| 22 #include "ColourDatabase.h" | |
| 23 #include "ColourMapper.h" | |
| 24 #include "LinearNumericalScale.h" | |
| 25 #include "LogNumericalScale.h" | |
| 26 #include "PaintAssistant.h" | |
| 27 | |
| 28 #include "view/View.h" | |
| 29 | |
| 30 #include "data/model/TimeFrequencyBoxModel.h" | |
| 31 | |
| 32 #include "widgets/ItemEditDialog.h" | |
| 33 #include "widgets/TextAbbrev.h" | |
| 34 | |
| 35 #include <QPainter> | |
| 36 #include <QPainterPath> | |
| 37 #include <QMouseEvent> | |
| 38 #include <QTextStream> | |
| 39 #include <QMessageBox> | |
| 40 | |
| 41 #include <iostream> | |
| 42 #include <cmath> | |
| 43 | |
| 44 TimeFrequencyBoxLayer::TimeFrequencyBoxLayer() : | |
| 45 SingleColourLayer(), | |
| 46 m_editing(false), | |
| 47 m_dragPointX(0), | |
| 48 m_dragPointY(0), | |
| 49 m_dragStartX(0), | |
| 50 m_dragStartY(0), | |
| 51 m_originalPoint(0, 0.0, 0, tr("New Box")), | |
| 52 m_editingPoint(0, 0.0, 0, tr("New Box")), | |
| 53 m_editingCommand(nullptr) | |
| 54 { | |
| 55 | |
| 56 } | |
| 57 | |
| 58 int | |
| 59 TimeFrequencyBoxLayer::getCompletion(LayerGeometryProvider *) const | |
| 60 { | |
| 61 auto model = ModelById::get(m_model); | |
| 62 if (model) return model->getCompletion(); | |
| 63 else return 0; | |
| 64 } | |
| 65 | |
| 66 void | |
| 67 TimeFrequencyBoxLayer::setModel(ModelId modelId) | |
| 68 { | |
| 69 auto oldModel = ModelById::getAs<TimeFrequencyBoxModel>(m_model); | |
| 70 auto newModel = ModelById::getAs<TimeFrequencyBoxModel>(modelId); | |
| 71 | |
| 72 if (!modelId.isNone() && !newModel) { | |
| 73 throw std::logic_error("Not a TimeFrequencyBoxModel"); | |
| 74 } | |
| 75 | |
| 76 if (m_model == modelId) return; | |
| 77 m_model = modelId; | |
| 78 | |
| 79 if (newModel) { | |
| 80 connectSignals(m_model); | |
| 81 } | |
| 82 | |
| 83 emit modelReplaced(); | |
| 84 } | |
| 85 | |
| 86 Layer::PropertyList | |
| 87 TimeFrequencyBoxLayer::getProperties() const | |
| 88 { | |
| 89 PropertyList list = SingleColourLayer::getProperties(); | |
| 90 list.push_back("Vertical Scale"); | |
| 91 return list; | |
| 92 } | |
| 93 | |
| 94 QString | |
| 95 TimeFrequencyBoxLayer::getPropertyLabel(const PropertyName &name) const | |
| 96 { | |
| 97 if (name == "Vertical Scale") return tr("Vertical Scale"); | |
| 98 return SingleColourLayer::getPropertyLabel(name); | |
| 99 } | |
| 100 | |
| 101 Layer::PropertyType | |
| 102 TimeFrequencyBoxLayer::getPropertyType(const PropertyName &name) const | |
| 103 { | |
| 104 if (name == "Vertical Scale") return ValueProperty; | |
| 105 return SingleColourLayer::getPropertyType(name); | |
| 106 } | |
| 107 | |
| 108 QString | |
| 109 TimeFrequencyBoxLayer::getPropertyGroupName(const PropertyName &name) const | |
| 110 { | |
| 111 if (name == "Vertical Scale") { | |
| 112 return tr("Scale"); | |
| 113 } | |
| 114 return SingleColourLayer::getPropertyGroupName(name); | |
| 115 } | |
| 116 | |
| 117 int | |
| 118 TimeFrequencyBoxLayer::getPropertyRangeAndValue(const PropertyName &name, | |
| 119 int *min, int *max, int *deflt) const | |
| 120 { | |
| 121 int val = 0; | |
| 122 | |
| 123 if (name == "Vertical Scale") { | |
| 124 | |
| 125 if (min) *min = 0; | |
| 126 if (max) *max = 2; | |
| 127 if (deflt) *deflt = int(LinearScale); | |
| 128 | |
| 129 val = int(m_verticalScale); | |
| 130 | |
| 131 } else { | |
| 132 val = SingleColourLayer::getPropertyRangeAndValue(name, min, max, deflt); | |
| 133 } | |
| 134 | |
| 135 return val; | |
| 136 } | |
| 137 | |
| 138 QString | |
| 139 TimeFrequencyBoxLayer::getPropertyValueLabel(const PropertyName &name, | |
| 140 int value) const | |
| 141 { | |
| 142 if (name == "Vertical Scale") { | |
| 143 switch (value) { | |
| 144 default: | |
| 145 case 0: return tr("Auto-Align"); | |
| 146 case 1: return tr("Linear"); | |
| 147 case 2: return tr("Log"); | |
| 148 } | |
| 149 } | |
| 150 return SingleColourLayer::getPropertyValueLabel(name, value); | |
| 151 } | |
| 152 | |
| 153 void | |
| 154 TimeFrequencyBoxLayer::setProperty(const PropertyName &name, int value) | |
| 155 { | |
| 156 if (name == "Vertical Scale") { | |
| 157 setVerticalScale(VerticalScale(value)); | |
| 158 } else { | |
| 159 return SingleColourLayer::setProperty(name, value); | |
| 160 } | |
| 161 } | |
| 162 | |
| 163 void | |
| 164 TimeFrequencyBoxLayer::setVerticalScale(VerticalScale scale) | |
| 165 { | |
| 166 if (m_verticalScale == scale) return; | |
| 167 m_verticalScale = scale; | |
| 168 emit layerParametersChanged(); | |
| 169 } | |
| 170 | |
| 171 bool | |
| 172 TimeFrequencyBoxLayer::isLayerScrollable(const LayerGeometryProvider *v) const | |
| 173 { | |
| 174 QPoint discard; | |
| 175 return !v->shouldIlluminateLocalFeatures(this, discard); | |
| 176 } | |
| 177 | |
| 178 bool | |
| 179 TimeFrequencyBoxLayer::getValueExtents(double &min, double &max, | |
| 180 bool &logarithmic, QString &unit) const | |
| 181 { | |
| 182 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); | |
| 183 if (!model) return false; | |
| 184 min = model->getFrequencyMinimum(); | |
| 185 max = model->getFrequencyMaximum(); | |
| 186 unit = getScaleUnits(); | |
| 187 | |
| 188 if (m_verticalScale == LogScale) logarithmic = true; | |
| 189 | |
| 190 return true; | |
| 191 } | |
| 192 | |
| 193 bool | |
| 194 TimeFrequencyBoxLayer::getDisplayExtents(double &min, double &max) const | |
| 195 { | |
| 196 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); | |
| 197 if (!model || m_verticalScale == AutoAlignScale) return false; | |
| 198 | |
| 199 min = model->getFrequencyMinimum(); | |
| 200 max = model->getFrequencyMaximum(); | |
| 201 | |
| 202 return true; | |
| 203 } | |
| 204 | |
| 205 EventVector | |
| 206 TimeFrequencyBoxLayer::getLocalPoints(LayerGeometryProvider *v, int x) const | |
| 207 { | |
| 208 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); | |
| 209 if (!model) return EventVector(); | |
| 210 | |
| 211 sv_frame_t frame = v->getFrameForX(x); | |
| 212 | |
| 213 EventVector local = model->getEventsCovering(frame); | |
| 214 if (!local.empty()) return local; | |
| 215 | |
| 216 int fuzz = ViewManager::scalePixelSize(2); | |
| 217 sv_frame_t start = v->getFrameForX(x - fuzz); | |
| 218 sv_frame_t end = v->getFrameForX(x + fuzz); | |
| 219 | |
| 220 local = model->getEventsStartingWithin(frame, end - frame); | |
| 221 if (!local.empty()) return local; | |
| 222 | |
| 223 local = model->getEventsSpanning(start, frame - start); | |
| 224 if (!local.empty()) return local; | |
| 225 | |
| 226 return {}; | |
| 227 } | |
| 228 | |
| 229 bool | |
| 230 TimeFrequencyBoxLayer::getPointToDrag(LayerGeometryProvider *v, int x, int y, | |
| 231 Event &point) const | |
| 232 { | |
| 233 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); | |
| 234 if (!model) return false; | |
| 235 | |
| 236 sv_frame_t frame = v->getFrameForX(x); | |
| 237 | |
| 238 EventVector onPoints = model->getEventsCovering(frame); | |
| 239 if (onPoints.empty()) return false; | |
| 240 | |
| 241 int nearestDistance = -1; | |
| 242 for (const auto &p: onPoints) { | |
| 243 int distance = getYForValue(v, p.getValue()) - y; | |
| 244 if (distance < 0) distance = -distance; | |
| 245 if (nearestDistance == -1 || distance < nearestDistance) { | |
| 246 nearestDistance = distance; | |
| 247 point = p; | |
| 248 } | |
| 249 } | |
| 250 | |
| 251 return true; | |
| 252 } | |
| 253 | |
| 254 QString | |
| 255 TimeFrequencyBoxLayer::getLabelPreceding(sv_frame_t frame) const | |
| 256 { | |
| 257 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); | |
| 258 if (!model) return ""; | |
| 259 EventVector points = model->getEventsStartingWithin | |
| 260 (model->getStartFrame(), frame - model->getStartFrame()); | |
| 261 if (!points.empty()) { | |
| 262 for (auto i = points.rbegin(); i != points.rend(); ++i) { | |
| 263 if (i->getLabel() != QString()) { | |
| 264 return i->getLabel(); | |
| 265 } | |
| 266 } | |
| 267 } | |
| 268 return QString(); | |
| 269 } | |
| 270 | |
| 271 QString | |
| 272 TimeFrequencyBoxLayer::getFeatureDescription(LayerGeometryProvider *v, | |
| 273 QPoint &pos) const | |
| 274 { | |
| 275 int x = pos.x(); | |
| 276 | |
| 277 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); | |
| 278 if (!model || !model->getSampleRate()) return ""; | |
| 279 | |
| 280 EventVector points = getLocalPoints(v, x); | |
| 281 | |
| 282 if (points.empty()) { | |
| 283 if (!model->isReady()) { | |
| 284 return tr("In progress"); | |
| 285 } else { | |
| 286 return tr("No local points"); | |
| 287 } | |
| 288 } | |
| 289 | |
| 290 Event box; | |
| 291 EventVector::iterator i; | |
| 292 | |
| 293 for (i = points.begin(); i != points.end(); ++i) { | |
| 294 | |
| 295 int y0 = getYForValue(v, i->getValue()); | |
| 296 int y1 = getYForValue(v, i->getValue() + fabsf(i->getLevel())); | |
| 297 | |
| 298 if (pos.y() >= y0 && pos.y() <= y1) { | |
| 299 box = *i; | |
| 300 break; | |
| 301 } | |
| 302 } | |
| 303 | |
| 304 if (i == points.end()) return tr("No local points"); | |
| 305 | |
| 306 RealTime rt = RealTime::frame2RealTime(box.getFrame(), | |
| 307 model->getSampleRate()); | |
| 308 RealTime rd = RealTime::frame2RealTime(box.getDuration(), | |
| 309 model->getSampleRate()); | |
| 310 | |
| 311 QString rangeText; | |
| 312 | |
| 313 rangeText = tr("%1 %2 - %3 %4") | |
| 314 .arg(box.getValue()).arg(getScaleUnits()) | |
| 315 .arg(box.getValue() + fabsf(box.getLevel())).arg(getScaleUnits()); | |
| 316 | |
| 317 QString text; | |
| 318 | |
| 319 if (box.getLabel() == "") { | |
| 320 text = QString(tr("Time:\t%1\nDuration:\t%2\nFrequency:\t%3\nNo label")) | |
| 321 .arg(rt.toText(true).c_str()) | |
| 322 .arg(rd.toText(true).c_str()) | |
| 323 .arg(rangeText); | |
| 324 } else { | |
| 325 text = QString(tr("Time:\t%1\nDuration:\t%2\nFrequency:\t%3\nLabel:\t%4")) | |
| 326 .arg(rt.toText(true).c_str()) | |
| 327 .arg(rd.toText(true).c_str()) | |
| 328 .arg(rangeText) | |
| 329 .arg(box.getLabel()); | |
| 330 } | |
| 331 | |
| 332 pos = QPoint(v->getXForFrame(box.getFrame()), | |
| 333 getYForValue(v, box.getValue())); | |
| 334 return text; | |
| 335 } | |
| 336 | |
| 337 bool | |
| 338 TimeFrequencyBoxLayer::snapToFeatureFrame(LayerGeometryProvider *v, | |
| 339 sv_frame_t &frame, | |
| 340 int &resolution, | |
| 341 SnapType snap) const | |
| 342 { | |
| 343 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); | |
| 344 if (!model) { | |
| 345 return Layer::snapToFeatureFrame(v, frame, resolution, snap); | |
| 346 } | |
| 347 | |
| 348 // SnapLeft / SnapRight: return frame of nearest feature in that | |
| 349 // direction no matter how far away | |
| 350 // | |
| 351 // SnapNeighbouring: return frame of feature that would be used in | |
| 352 // an editing operation, i.e. closest feature in either direction | |
| 353 // but only if it is "close enough" | |
| 354 | |
| 355 resolution = model->getResolution(); | |
| 356 | |
| 357 if (snap == SnapNeighbouring) { | |
| 358 EventVector points = getLocalPoints(v, v->getXForFrame(frame)); | |
| 359 if (points.empty()) return false; | |
| 360 frame = points.begin()->getFrame(); | |
| 361 return true; | |
| 362 } | |
| 363 | |
| 364 // Normally we snap to the start frame of whichever event we | |
| 365 // find. However here, for SnapRight only, if the end frame of | |
| 366 // whichever event we would have snapped to had we been snapping | |
| 367 // left is closer than the start frame of the next event to the | |
| 368 // right, then we snap to that frame instead. Clear? | |
| 369 | |
| 370 Event left; | |
| 371 bool haveLeft = false; | |
| 372 if (model->getNearestEventMatching | |
| 373 (frame, [](Event) { return true; }, EventSeries::Backward, left)) { | |
| 374 haveLeft = true; | |
| 375 } | |
| 376 | |
| 377 if (snap == SnapLeft) { | |
| 378 frame = left.getFrame(); | |
| 379 return haveLeft; | |
| 380 } | |
| 381 | |
| 382 Event right; | |
| 383 bool haveRight = false; | |
| 384 if (model->getNearestEventMatching | |
| 385 (frame, [](Event) { return true; }, EventSeries::Forward, right)) { | |
| 386 haveRight = true; | |
| 387 } | |
| 388 | |
| 389 if (haveLeft) { | |
| 390 sv_frame_t leftEnd = left.getFrame() + left.getDuration(); | |
| 391 if (leftEnd > frame) { | |
| 392 if (haveRight) { | |
| 393 if (leftEnd - frame < right.getFrame() - frame) { | |
| 394 frame = leftEnd; | |
| 395 } else { | |
| 396 frame = right.getFrame(); | |
| 397 } | |
| 398 } else { | |
| 399 frame = leftEnd; | |
| 400 } | |
| 401 return true; | |
| 402 } | |
| 403 } | |
| 404 | |
| 405 if (haveRight) { | |
| 406 frame = right.getFrame(); | |
| 407 return true; | |
| 408 } | |
| 409 | |
| 410 return false; | |
| 411 } | |
| 412 | |
| 413 QString | |
| 414 TimeFrequencyBoxLayer::getScaleUnits() const | |
| 415 { | |
| 416 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); | |
| 417 if (model) return model->getScaleUnits(); | |
| 418 else return ""; | |
| 419 } | |
| 420 | |
| 421 void | |
| 422 TimeFrequencyBoxLayer::getScaleExtents(LayerGeometryProvider *v, | |
| 423 double &min, double &max, | |
| 424 bool &log) const | |
| 425 { | |
| 426 min = 0.0; | |
| 427 max = 0.0; | |
| 428 log = false; | |
| 429 | |
| 430 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); | |
| 431 if (!model) return; | |
| 432 | |
| 433 QString queryUnits; | |
| 434 queryUnits = getScaleUnits(); | |
| 435 | |
| 436 if (m_verticalScale == AutoAlignScale) { | |
| 437 | |
| 438 if (!v->getValueExtents(queryUnits, min, max, log)) { | |
| 439 | |
| 440 min = model->getFrequencyMinimum(); | |
| 441 max = model->getFrequencyMaximum(); | |
| 442 | |
| 443 // cerr << "TimeFrequencyBoxLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << endl; | |
| 444 | |
| 445 } else if (log) { | |
| 446 | |
| 447 LogRange::mapRange(min, max); | |
| 448 | |
| 449 // cerr << "TimeFrequencyBoxLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << endl; | |
| 450 | |
| 451 } | |
| 452 | |
| 453 } else { | |
| 454 | |
| 455 min = model->getFrequencyMinimum(); | |
| 456 max = model->getFrequencyMaximum(); | |
| 457 | |
| 458 if (m_verticalScale == LogScale) { | |
| 459 LogRange::mapRange(min, max); | |
| 460 log = true; | |
| 461 } | |
| 462 } | |
| 463 | |
| 464 if (max == min) max = min + 1.0; | |
| 465 } | |
| 466 | |
| 467 int | |
| 468 TimeFrequencyBoxLayer::getYForValue(LayerGeometryProvider *v, double val) const | |
| 469 { | |
| 470 double min = 0.0, max = 0.0; | |
| 471 bool logarithmic = false; | |
| 472 int h = v->getPaintHeight(); | |
| 473 | |
| 474 getScaleExtents(v, min, max, logarithmic); | |
| 475 | |
| 476 // cerr << "TimeFrequencyBoxLayer[" << this << "]::getYForValue(" << val << "): min = " << min << ", max = " << max << ", log = " << logarithmic << endl; | |
| 477 // cerr << "h = " << h << ", margin = " << margin << endl; | |
| 478 | |
| 479 if (logarithmic) { | |
| 480 val = LogRange::map(val); | |
| 481 } | |
| 482 | |
| 483 return int(h - ((val - min) * h) / (max - min)); | |
| 484 } | |
| 485 | |
| 486 double | |
| 487 TimeFrequencyBoxLayer::getValueForY(LayerGeometryProvider *v, int y) const | |
| 488 { | |
| 489 double min = 0.0, max = 0.0; | |
| 490 bool logarithmic = false; | |
| 491 int h = v->getPaintHeight(); | |
| 492 | |
| 493 getScaleExtents(v, min, max, logarithmic); | |
| 494 | |
| 495 double val = min + (double(h - y) * double(max - min)) / h; | |
| 496 | |
| 497 if (logarithmic) { | |
| 498 val = pow(10.0, val); | |
| 499 } | |
| 500 | |
| 501 return val; | |
| 502 } | |
| 503 | |
| 504 void | |
| 505 TimeFrequencyBoxLayer::paint(LayerGeometryProvider *v, QPainter &paint, | |
| 506 QRect rect) const | |
| 507 { | |
| 508 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); | |
| 509 if (!model || !model->isOK()) return; | |
| 510 | |
| 511 sv_samplerate_t sampleRate = model->getSampleRate(); | |
| 512 if (!sampleRate) return; | |
| 513 | |
| 514 // Profiler profiler("TimeFrequencyBoxLayer::paint", true); | |
| 515 | |
| 516 int x0 = rect.left() - 40, x1 = rect.right(); | |
| 517 | |
| 518 sv_frame_t wholeFrame0 = v->getFrameForX(0); | |
| 519 sv_frame_t wholeFrame1 = v->getFrameForX(v->getPaintWidth()); | |
| 520 | |
| 521 EventVector points(model->getEventsSpanning(wholeFrame0, | |
| 522 wholeFrame1 - wholeFrame0)); | |
| 523 if (points.empty()) return; | |
| 524 | |
| 525 paint.setPen(getBaseQColor()); | |
| 526 | |
| 527 QColor brushColour(getBaseQColor()); | |
| 528 brushColour.setAlpha(80); | |
| 529 | |
| 530 // SVDEBUG << "TimeFrequencyBoxLayer::paint: resolution is " | |
| 531 // << model->getResolution() << " frames" << endl; | |
| 532 | |
| 533 double min = model->getFrequencyMinimum(); | |
| 534 double max = model->getFrequencyMaximum(); | |
| 535 if (max == min) max = min + 1.0; | |
| 536 | |
| 537 QPoint localPos; | |
| 538 Event illuminatePoint(0); | |
| 539 bool shouldIlluminate = false; | |
| 540 | |
| 541 if (v->shouldIlluminateLocalFeatures(this, localPos)) { | |
| 542 shouldIlluminate = getPointToDrag(v, localPos.x(), localPos.y(), | |
| 543 illuminatePoint); | |
| 544 } | |
| 545 | |
| 546 paint.save(); | |
| 547 paint.setRenderHint(QPainter::Antialiasing, false); | |
| 548 | |
| 549 for (EventVector::const_iterator i = points.begin(); | |
| 550 i != points.end(); ++i) { | |
| 551 | |
| 552 const Event &p(*i); | |
| 553 | |
| 554 int x = v->getXForFrame(p.getFrame()); | |
| 555 int w = v->getXForFrame(p.getFrame() + p.getDuration()) - x; | |
| 556 int y = getYForValue(v, p.getValue()); | |
| 557 int h = getYForValue(v, p.getValue() + fabsf(p.getLevel())); | |
| 558 int ex = x + w; | |
| 559 | |
| 560 int gap = v->scalePixelSize(2); | |
| 561 | |
| 562 EventVector::const_iterator j = i; | |
| 563 ++j; | |
| 564 | |
| 565 if (j != points.end()) { | |
| 566 const Event &q(*j); | |
| 567 int nx = v->getXForFrame(q.getFrame()); | |
| 568 if (nx < ex) ex = nx; | |
| 569 } | |
| 570 | |
| 571 if (w < 1) w = 1; | |
| 572 if (h < 1) h = 1; | |
| 573 | |
| 574 paint.setPen(getBaseQColor()); | |
| 575 paint.setBrush(brushColour); | |
| 576 | |
| 577 if (shouldIlluminate && illuminatePoint == p) { | |
| 578 | |
| 579 paint.setPen(v->getForeground()); | |
| 580 paint.setBrush(v->getForeground()); | |
| 581 | |
| 582 // Qt 5.13 deprecates QFontMetrics::width(), but its suggested | |
| 583 // replacement (horizontalAdvance) was only added in Qt 5.11 | |
| 584 // which is too new for us | |
| 585 #pragma GCC diagnostic ignored "-Wdeprecated-declarations" | |
| 586 | |
| 587 QString vlabel = | |
| 588 QString("%1%2 - %3%4") | |
| 589 .arg(p.getValue()).arg(getScaleUnits()) | |
| 590 .arg(p.getValue() + fabsf(p.getLevel())).arg(getScaleUnits()); | |
| 591 PaintAssistant::drawVisibleText | |
| 592 (v, paint, | |
| 593 x - paint.fontMetrics().width(vlabel) - gap, | |
| 594 y + paint.fontMetrics().height()/2 | |
| 595 - paint.fontMetrics().descent(), | |
| 596 vlabel, PaintAssistant::OutlinedText); | |
| 597 | |
| 598 QString hlabel = RealTime::frame2RealTime | |
| 599 (p.getFrame(), model->getSampleRate()).toText(true).c_str(); | |
| 600 PaintAssistant::drawVisibleText | |
| 601 (v, paint, | |
| 602 x, | |
| 603 y - h/2 - paint.fontMetrics().descent() - gap, | |
| 604 hlabel, PaintAssistant::OutlinedText); | |
| 605 } | |
| 606 | |
| 607 paint.drawRect(x, y, w, h); | |
| 608 } | |
| 609 | |
| 610 for (EventVector::const_iterator i = points.begin(); | |
| 611 i != points.end(); ++i) { | |
| 612 | |
| 613 const Event &p(*i); | |
| 614 | |
| 615 int x = v->getXForFrame(p.getFrame()); | |
| 616 int w = v->getXForFrame(p.getFrame() + p.getDuration()) - x; | |
| 617 int y = getYForValue(v, p.getValue()); | |
| 618 | |
| 619 QString label = p.getLabel(); | |
| 620 if (label == "") { | |
| 621 label = | |
| 622 QString("%1%2 - %3%4") | |
| 623 .arg(p.getValue()).arg(getScaleUnits()) | |
| 624 .arg(p.getValue() + fabsf(p.getLevel())).arg(getScaleUnits()); | |
| 625 } | |
| 626 int labelWidth = paint.fontMetrics().width(label); | |
| 627 | |
| 628 int gap = v->scalePixelSize(2); | |
| 629 | |
| 630 if (x + w < x0 || x - labelWidth - gap > x1) { | |
| 631 continue; | |
| 632 } | |
| 633 | |
| 634 bool illuminated = false; | |
| 635 | |
| 636 if (shouldIlluminate && illuminatePoint == p) { | |
| 637 illuminated = true; | |
| 638 } | |
| 639 | |
| 640 if (!illuminated) { | |
| 641 | |
| 642 int labelX, labelY; | |
| 643 | |
| 644 labelX = x - labelWidth - gap; | |
| 645 labelY = y + paint.fontMetrics().height()/2 | |
| 646 - paint.fontMetrics().descent(); | |
| 647 | |
| 648 PaintAssistant::drawVisibleText(v, paint, labelX, labelY, label, | |
| 649 PaintAssistant::OutlinedText); | |
| 650 } | |
| 651 } | |
| 652 | |
| 653 paint.restore(); | |
| 654 } | |
| 655 | |
| 656 int | |
| 657 TimeFrequencyBoxLayer::getVerticalScaleWidth(LayerGeometryProvider *v, | |
| 658 bool, QPainter &paint) const | |
| 659 { | |
| 660 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); | |
| 661 if (!model || m_verticalScale == AutoAlignScale) { | |
| 662 return 0; | |
| 663 } else { | |
| 664 if (m_verticalScale == LogScale) { | |
| 665 return LogNumericalScale().getWidth(v, paint); | |
| 666 } else { | |
| 667 return LinearNumericalScale().getWidth(v, paint); | |
| 668 } | |
| 669 } | |
| 670 } | |
| 671 | |
| 672 void | |
| 673 TimeFrequencyBoxLayer::paintVerticalScale(LayerGeometryProvider *v, | |
| 674 bool, QPainter &paint, QRect) const | |
| 675 { | |
| 676 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); | |
| 677 if (!model || model->isEmpty()) return; | |
| 678 | |
| 679 QString unit; | |
| 680 double min, max; | |
| 681 bool logarithmic; | |
| 682 | |
| 683 int w = getVerticalScaleWidth(v, false, paint); | |
| 684 | |
| 685 getScaleExtents(v, min, max, logarithmic); | |
| 686 | |
| 687 if (logarithmic) { | |
| 688 LogNumericalScale().paintVertical(v, this, paint, 0, min, max); | |
| 689 } else { | |
| 690 LinearNumericalScale().paintVertical(v, this, paint, 0, min, max); | |
| 691 } | |
| 692 | |
| 693 if (getScaleUnits() != "") { | |
| 694 int mw = w - 5; | |
| 695 paint.drawText(5, | |
| 696 5 + paint.fontMetrics().ascent(), | |
| 697 TextAbbrev::abbreviate(getScaleUnits(), | |
| 698 paint.fontMetrics(), | |
| 699 mw)); | |
| 700 } | |
| 701 } | |
| 702 | |
| 703 //!!! All of the editing operations still need to be updated for | |
| 704 //!!! vertical frequency range instead of just value | |
| 705 | |
| 706 void | |
| 707 TimeFrequencyBoxLayer::drawStart(LayerGeometryProvider *v, QMouseEvent *e) | |
| 708 { | |
| 709 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); | |
| 710 if (!model) return; | |
| 711 | |
| 712 sv_frame_t frame = v->getFrameForX(e->x()); | |
| 713 if (frame < 0) frame = 0; | |
| 714 frame = frame / model->getResolution() * model->getResolution(); | |
| 715 | |
| 716 double value = getValueForY(v, e->y()); | |
| 717 | |
| 718 m_editingPoint = Event(frame, float(value), 0, ""); | |
| 719 m_originalPoint = m_editingPoint; | |
| 720 | |
| 721 if (m_editingCommand) finish(m_editingCommand); | |
| 722 m_editingCommand = new ChangeEventsCommand(m_model.untyped, | |
| 723 tr("Draw Time-Frequency Box")); | |
| 724 m_editingCommand->add(m_editingPoint); | |
| 725 | |
| 726 m_editing = true; | |
| 727 } | |
| 728 | |
| 729 void | |
| 730 TimeFrequencyBoxLayer::drawDrag(LayerGeometryProvider *v, QMouseEvent *e) | |
| 731 { | |
| 732 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); | |
| 733 if (!model || !m_editing) return; | |
| 734 | |
| 735 sv_frame_t frame = v->getFrameForX(e->x()); | |
| 736 if (frame < 0) frame = 0; | |
| 737 frame = frame / model->getResolution() * model->getResolution(); | |
| 738 | |
| 739 double newValue = getValueForY(v, e->y()); | |
| 740 | |
| 741 sv_frame_t newFrame = m_editingPoint.getFrame(); | |
| 742 sv_frame_t newDuration = frame - newFrame; | |
| 743 if (newDuration < 0) { | |
| 744 newFrame = frame; | |
| 745 newDuration = -newDuration; | |
| 746 } else if (newDuration == 0) { | |
| 747 newDuration = 1; | |
| 748 } | |
| 749 | |
| 750 m_editingCommand->remove(m_editingPoint); | |
| 751 m_editingPoint = m_editingPoint | |
| 752 .withFrame(newFrame) | |
| 753 .withValue(float(newValue)) | |
| 754 .withDuration(newDuration); | |
| 755 m_editingCommand->add(m_editingPoint); | |
| 756 } | |
| 757 | |
| 758 void | |
| 759 TimeFrequencyBoxLayer::drawEnd(LayerGeometryProvider *, QMouseEvent *) | |
| 760 { | |
| 761 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); | |
| 762 if (!model || !m_editing) return; | |
| 763 finish(m_editingCommand); | |
| 764 m_editingCommand = nullptr; | |
| 765 m_editing = false; | |
| 766 } | |
| 767 | |
| 768 void | |
| 769 TimeFrequencyBoxLayer::eraseStart(LayerGeometryProvider *v, QMouseEvent *e) | |
| 770 { | |
| 771 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); | |
| 772 if (!model) return; | |
| 773 | |
| 774 if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return; | |
| 775 | |
| 776 if (m_editingCommand) { | |
| 777 finish(m_editingCommand); | |
| 778 m_editingCommand = nullptr; | |
| 779 } | |
| 780 | |
| 781 m_editing = true; | |
| 782 } | |
| 783 | |
| 784 void | |
| 785 TimeFrequencyBoxLayer::eraseDrag(LayerGeometryProvider *, QMouseEvent *) | |
| 786 { | |
| 787 } | |
| 788 | |
| 789 void | |
| 790 TimeFrequencyBoxLayer::eraseEnd(LayerGeometryProvider *v, QMouseEvent *e) | |
| 791 { | |
| 792 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); | |
| 793 if (!model || !m_editing) return; | |
| 794 | |
| 795 m_editing = false; | |
| 796 | |
| 797 Event p(0); | |
| 798 if (!getPointToDrag(v, e->x(), e->y(), p)) return; | |
| 799 if (p.getFrame() != m_editingPoint.getFrame() || | |
| 800 p.getValue() != m_editingPoint.getValue()) return; | |
| 801 | |
| 802 m_editingCommand = new ChangeEventsCommand | |
| 803 (m_model.untyped, tr("Erase Time-Frequency Box")); | |
| 804 | |
| 805 m_editingCommand->remove(m_editingPoint); | |
| 806 | |
| 807 finish(m_editingCommand); | |
| 808 m_editingCommand = nullptr; | |
| 809 m_editing = false; | |
| 810 } | |
| 811 | |
| 812 void | |
| 813 TimeFrequencyBoxLayer::editStart(LayerGeometryProvider *v, QMouseEvent *e) | |
| 814 { | |
| 815 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); | |
| 816 if (!model) return; | |
| 817 | |
| 818 if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) { | |
| 819 return; | |
| 820 } | |
| 821 | |
| 822 m_dragPointX = v->getXForFrame(m_editingPoint.getFrame()); | |
| 823 m_dragPointY = getYForValue(v, m_editingPoint.getValue()); | |
| 824 | |
| 825 m_originalPoint = m_editingPoint; | |
| 826 | |
| 827 if (m_editingCommand) { | |
| 828 finish(m_editingCommand); | |
| 829 m_editingCommand = nullptr; | |
| 830 } | |
| 831 | |
| 832 m_editing = true; | |
| 833 m_dragStartX = e->x(); | |
| 834 m_dragStartY = e->y(); | |
| 835 } | |
| 836 | |
| 837 void | |
| 838 TimeFrequencyBoxLayer::editDrag(LayerGeometryProvider *v, QMouseEvent *e) | |
| 839 { | |
| 840 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); | |
| 841 if (!model || !m_editing) return; | |
| 842 | |
| 843 int xdist = e->x() - m_dragStartX; | |
| 844 int ydist = e->y() - m_dragStartY; | |
| 845 int newx = m_dragPointX + xdist; | |
| 846 int newy = m_dragPointY + ydist; | |
| 847 | |
| 848 sv_frame_t frame = v->getFrameForX(newx); | |
| 849 if (frame < 0) frame = 0; | |
| 850 frame = frame / model->getResolution() * model->getResolution(); | |
| 851 | |
| 852 double value = getValueForY(v, newy); | |
| 853 | |
| 854 if (!m_editingCommand) { | |
| 855 m_editingCommand = new ChangeEventsCommand | |
| 856 (m_model.untyped, | |
| 857 tr("Drag Time-Frequency Box")); | |
| 858 } | |
| 859 | |
| 860 m_editingCommand->remove(m_editingPoint); | |
| 861 m_editingPoint = m_editingPoint | |
| 862 .withFrame(frame) | |
| 863 .withValue(float(value)); | |
| 864 m_editingCommand->add(m_editingPoint); | |
| 865 } | |
| 866 | |
| 867 void | |
| 868 TimeFrequencyBoxLayer::editEnd(LayerGeometryProvider *, QMouseEvent *) | |
| 869 { | |
| 870 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); | |
| 871 if (!model || !m_editing) return; | |
| 872 | |
| 873 if (m_editingCommand) { | |
| 874 | |
| 875 QString newName = m_editingCommand->getName(); | |
| 876 | |
| 877 if (m_editingPoint.getFrame() != m_originalPoint.getFrame()) { | |
| 878 if (m_editingPoint.getValue() != m_originalPoint.getValue()) { | |
| 879 newName = tr("Edit Time-Frequency Box"); | |
| 880 } else { | |
| 881 newName = tr("Relocate Time-Frequency Box"); | |
| 882 } | |
| 883 } else { | |
| 884 newName = tr("Change Point Value"); | |
| 885 } | |
| 886 | |
| 887 m_editingCommand->setName(newName); | |
| 888 finish(m_editingCommand); | |
| 889 } | |
| 890 | |
| 891 m_editingCommand = nullptr; | |
| 892 m_editing = false; | |
| 893 } | |
| 894 | |
| 895 bool | |
| 896 TimeFrequencyBoxLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e) | |
| 897 { | |
| 898 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); | |
| 899 if (!model) return false; | |
| 900 | |
| 901 Event region(0); | |
| 902 if (!getPointToDrag(v, e->x(), e->y(), region)) return false; | |
| 903 | |
| 904 ItemEditDialog *dialog = new ItemEditDialog | |
| 905 (model->getSampleRate(), | |
| 906 ItemEditDialog::ShowTime | | |
| 907 ItemEditDialog::ShowDuration | | |
| 908 ItemEditDialog::ShowValue | | |
| 909 ItemEditDialog::ShowText, | |
| 910 getScaleUnits()); | |
| 911 | |
| 912 dialog->setFrameTime(region.getFrame()); | |
| 913 dialog->setValue(region.getValue()); | |
| 914 dialog->setFrameDuration(region.getDuration()); | |
| 915 dialog->setText(region.getLabel()); | |
| 916 | |
| 917 if (dialog->exec() == QDialog::Accepted) { | |
| 918 | |
| 919 Event newTimeFrequencyBox = region | |
| 920 .withFrame(dialog->getFrameTime()) | |
| 921 .withValue(dialog->getValue()) | |
| 922 .withDuration(dialog->getFrameDuration()) | |
| 923 .withLabel(dialog->getText()); | |
| 924 | |
| 925 ChangeEventsCommand *command = new ChangeEventsCommand | |
| 926 (m_model.untyped, tr("Edit Time-Frequency Box")); | |
| 927 command->remove(region); | |
| 928 command->add(newTimeFrequencyBox); | |
| 929 finish(command); | |
| 930 } | |
| 931 | |
| 932 delete dialog; | |
| 933 return true; | |
| 934 } | |
| 935 | |
| 936 void | |
| 937 TimeFrequencyBoxLayer::moveSelection(Selection s, sv_frame_t newStartFrame) | |
| 938 { | |
| 939 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); | |
| 940 if (!model) return; | |
| 941 | |
| 942 ChangeEventsCommand *command = | |
| 943 new ChangeEventsCommand(m_model.untyped, tr("Drag Selection")); | |
| 944 | |
| 945 EventVector points = | |
| 946 model->getEventsStartingWithin(s.getStartFrame(), s.getDuration()); | |
| 947 | |
| 948 for (EventVector::iterator i = points.begin(); | |
| 949 i != points.end(); ++i) { | |
| 950 | |
| 951 Event newPoint = (*i) | |
| 952 .withFrame(i->getFrame() + newStartFrame - s.getStartFrame()); | |
| 953 command->remove(*i); | |
| 954 command->add(newPoint); | |
| 955 } | |
| 956 | |
| 957 finish(command); | |
| 958 } | |
| 959 | |
| 960 void | |
| 961 TimeFrequencyBoxLayer::resizeSelection(Selection s, Selection newSize) | |
| 962 { | |
| 963 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); | |
| 964 if (!model || !s.getDuration()) return; | |
| 965 | |
| 966 ChangeEventsCommand *command = | |
| 967 new ChangeEventsCommand(m_model.untyped, tr("Resize Selection")); | |
| 968 | |
| 969 EventVector points = | |
| 970 model->getEventsStartingWithin(s.getStartFrame(), s.getDuration()); | |
| 971 | |
| 972 double ratio = double(newSize.getDuration()) / double(s.getDuration()); | |
| 973 double oldStart = double(s.getStartFrame()); | |
| 974 double newStart = double(newSize.getStartFrame()); | |
| 975 | |
| 976 for (Event p: points) { | |
| 977 | |
| 978 double newFrame = (double(p.getFrame()) - oldStart) * ratio + newStart; | |
| 979 double newDuration = double(p.getDuration()) * ratio; | |
| 980 | |
| 981 Event newPoint = p | |
| 982 .withFrame(lrint(newFrame)) | |
| 983 .withDuration(lrint(newDuration)); | |
| 984 command->remove(p); | |
| 985 command->add(newPoint); | |
| 986 } | |
| 987 | |
| 988 finish(command); | |
| 989 } | |
| 990 | |
| 991 void | |
| 992 TimeFrequencyBoxLayer::deleteSelection(Selection s) | |
| 993 { | |
| 994 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); | |
| 995 if (!model) return; | |
| 996 | |
| 997 ChangeEventsCommand *command = | |
| 998 new ChangeEventsCommand(m_model.untyped, tr("Delete Selected Points")); | |
| 999 | |
| 1000 EventVector points = | |
| 1001 model->getEventsStartingWithin(s.getStartFrame(), s.getDuration()); | |
| 1002 | |
| 1003 for (EventVector::iterator i = points.begin(); | |
| 1004 i != points.end(); ++i) { | |
| 1005 | |
| 1006 if (s.contains(i->getFrame())) { | |
| 1007 command->remove(*i); | |
| 1008 } | |
| 1009 } | |
| 1010 | |
| 1011 finish(command); | |
| 1012 } | |
| 1013 | |
| 1014 void | |
| 1015 TimeFrequencyBoxLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to) | |
| 1016 { | |
| 1017 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); | |
| 1018 if (!model) return; | |
| 1019 | |
| 1020 EventVector points = | |
| 1021 model->getEventsStartingWithin(s.getStartFrame(), s.getDuration()); | |
| 1022 | |
| 1023 for (Event p: points) { | |
| 1024 to.addPoint(p.withReferenceFrame(alignToReference(v, p.getFrame()))); | |
| 1025 } | |
| 1026 } | |
| 1027 | |
| 1028 bool | |
| 1029 TimeFrequencyBoxLayer::paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t /* frameOffset */, bool /* interactive */) | |
| 1030 { | |
| 1031 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model); | |
| 1032 if (!model) return false; | |
| 1033 | |
| 1034 const EventVector &points = from.getPoints(); | |
| 1035 | |
| 1036 bool realign = false; | |
| 1037 | |
| 1038 if (clipboardHasDifferentAlignment(v, from)) { | |
| 1039 | |
| 1040 QMessageBox::StandardButton button = | |
| 1041 QMessageBox::question(v->getView(), tr("Re-align pasted items?"), | |
| 1042 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?"), | |
| 1043 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, | |
| 1044 QMessageBox::Yes); | |
| 1045 | |
| 1046 if (button == QMessageBox::Cancel) { | |
| 1047 return false; | |
| 1048 } | |
| 1049 | |
| 1050 if (button == QMessageBox::Yes) { | |
| 1051 realign = true; | |
| 1052 } | |
| 1053 } | |
| 1054 | |
| 1055 ChangeEventsCommand *command = | |
| 1056 new ChangeEventsCommand(m_model.untyped, tr("Paste")); | |
| 1057 | |
| 1058 for (EventVector::const_iterator i = points.begin(); | |
| 1059 i != points.end(); ++i) { | |
| 1060 | |
| 1061 sv_frame_t frame = 0; | |
| 1062 | |
| 1063 if (!realign) { | |
| 1064 | |
| 1065 frame = i->getFrame(); | |
| 1066 | |
| 1067 } else { | |
| 1068 | |
| 1069 if (i->hasReferenceFrame()) { | |
| 1070 frame = i->getReferenceFrame(); | |
| 1071 frame = alignFromReference(v, frame); | |
| 1072 } else { | |
| 1073 frame = i->getFrame(); | |
| 1074 } | |
| 1075 } | |
| 1076 | |
| 1077 Event p = *i; | |
| 1078 Event newPoint = p; | |
| 1079 if (!p.hasValue()) { | |
| 1080 newPoint = newPoint.withValue((model->getFrequencyMinimum() + | |
| 1081 model->getFrequencyMaximum()) / 2); | |
| 1082 } | |
| 1083 if (!p.hasDuration()) { | |
| 1084 sv_frame_t nextFrame = frame; | |
| 1085 EventVector::const_iterator j = i; | |
| 1086 for (; j != points.end(); ++j) { | |
| 1087 if (j != i) break; | |
| 1088 } | |
| 1089 if (j != points.end()) { | |
| 1090 nextFrame = j->getFrame(); | |
| 1091 } | |
| 1092 if (nextFrame == frame) { | |
| 1093 newPoint = newPoint.withDuration(model->getResolution()); | |
| 1094 } else { | |
| 1095 newPoint = newPoint.withDuration(nextFrame - frame); | |
| 1096 } | |
| 1097 } | |
| 1098 | |
| 1099 command->add(newPoint); | |
| 1100 } | |
| 1101 | |
| 1102 finish(command); | |
| 1103 return true; | |
| 1104 } | |
| 1105 | |
| 1106 void | |
| 1107 TimeFrequencyBoxLayer::toXml(QTextStream &stream, | |
| 1108 QString indent, QString extraAttributes) const | |
| 1109 { | |
| 1110 QString s; | |
| 1111 | |
| 1112 s += QString("verticalScale=\"%1\" ") | |
| 1113 .arg(m_verticalScale); | |
| 1114 | |
| 1115 SingleColourLayer::toXml(stream, indent, extraAttributes + " " + s); | |
| 1116 } | |
| 1117 | |
| 1118 void | |
| 1119 TimeFrequencyBoxLayer::setProperties(const QXmlAttributes &attributes) | |
| 1120 { | |
| 1121 SingleColourLayer::setProperties(attributes); | |
| 1122 | |
| 1123 bool ok; | |
| 1124 VerticalScale scale = (VerticalScale) | |
| 1125 attributes.value("verticalScale").toInt(&ok); | |
| 1126 if (ok) setVerticalScale(scale); | |
| 1127 } | |
| 1128 | |
| 1129 |
