annotate layer/NoteLayer.cpp @ 349:369a197737c7

* Various fixes to object lifetime management, particularly in the spectrum layer and for notification of main model deletion. The main purpose of this is to improve the behaviour of the spectrum, but I think it may also help with #1840922 Various crashes in Layer Summary window.
author Chris Cannam
date Wed, 23 Jan 2008 15:43:27 +0000
parents 2f83b6e3b8ca
children 020c485aa7e0 0895517bb2d1
rev   line source
Chris@58 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@30 2
Chris@30 3 /*
Chris@59 4 Sonic Visualiser
Chris@59 5 An audio file viewer and annotation editor.
Chris@59 6 Centre for Digital Music, Queen Mary, University of London.
Chris@59 7 This file copyright 2006 Chris Cannam.
Chris@30 8
Chris@59 9 This program is free software; you can redistribute it and/or
Chris@59 10 modify it under the terms of the GNU General Public License as
Chris@59 11 published by the Free Software Foundation; either version 2 of the
Chris@59 12 License, or (at your option) any later version. See the file
Chris@59 13 COPYING included with this distribution for more information.
Chris@30 14 */
Chris@30 15
Chris@30 16 #include "NoteLayer.h"
Chris@30 17
Chris@128 18 #include "data/model/Model.h"
Chris@30 19 #include "base/RealTime.h"
Chris@30 20 #include "base/Profiler.h"
Chris@30 21 #include "base/Pitch.h"
Chris@197 22 #include "base/LogRange.h"
Chris@287 23 #include "base/ColourDatabase.h"
Chris@128 24 #include "view/View.h"
Chris@30 25
Chris@128 26 #include "data/model/NoteModel.h"
Chris@30 27
Chris@70 28 #include "widgets/ItemEditDialog.h"
Chris@70 29
Chris@42 30 #include "SpectrogramLayer.h" // for optional frequency alignment
Chris@42 31
Chris@30 32 #include <QPainter>
Chris@30 33 #include <QPainterPath>
Chris@30 34 #include <QMouseEvent>
Chris@316 35 #include <QTextStream>
Chris@30 36
Chris@30 37 #include <iostream>
Chris@30 38 #include <cmath>
Chris@30 39
Chris@44 40 NoteLayer::NoteLayer() :
Chris@287 41 SingleColourLayer(),
Chris@30 42 m_model(0),
Chris@30 43 m_editing(false),
Chris@335 44 m_originalPoint(0, 0.0, 0, 1.f, tr("New Point")),
Chris@335 45 m_editingPoint(0, 0.0, 0, 1.f, tr("New Point")),
Chris@30 46 m_editingCommand(0),
Chris@101 47 m_verticalScale(AutoAlignScale)
Chris@30 48 {
Chris@44 49
Chris@30 50 }
Chris@30 51
Chris@30 52 void
Chris@30 53 NoteLayer::setModel(NoteModel *model)
Chris@30 54 {
Chris@30 55 if (m_model == model) return;
Chris@30 56 m_model = model;
Chris@30 57
Chris@320 58 connectSignals(m_model);
Chris@30 59
Chris@101 60 // std::cerr << "NoteLayer::setModel(" << model << ")" << std::endl;
Chris@30 61
Chris@30 62 emit modelReplaced();
Chris@30 63 }
Chris@30 64
Chris@30 65 Layer::PropertyList
Chris@30 66 NoteLayer::getProperties() const
Chris@30 67 {
Chris@287 68 PropertyList list = SingleColourLayer::getProperties();
Chris@87 69 list.push_back("Vertical Scale");
Chris@100 70 list.push_back("Scale Units");
Chris@30 71 return list;
Chris@30 72 }
Chris@30 73
Chris@87 74 QString
Chris@87 75 NoteLayer::getPropertyLabel(const PropertyName &name) const
Chris@87 76 {
Chris@87 77 if (name == "Vertical Scale") return tr("Vertical Scale");
Chris@116 78 if (name == "Scale Units") return tr("Scale Units");
Chris@287 79 return SingleColourLayer::getPropertyLabel(name);
Chris@87 80 }
Chris@87 81
Chris@30 82 Layer::PropertyType
Chris@100 83 NoteLayer::getPropertyType(const PropertyName &name) const
Chris@30 84 {
Chris@100 85 if (name == "Scale Units") return UnitsProperty;
Chris@287 86 if (name == "Vertical Scale") return ValueProperty;
Chris@287 87 return SingleColourLayer::getPropertyType(name);
Chris@30 88 }
Chris@30 89
Chris@198 90 QString
Chris@198 91 NoteLayer::getPropertyGroupName(const PropertyName &name) const
Chris@198 92 {
Chris@198 93 if (name == "Vertical Scale" || name == "Scale Units") {
Chris@198 94 return tr("Scale");
Chris@198 95 }
Chris@287 96 return SingleColourLayer::getPropertyGroupName(name);
Chris@198 97 }
Chris@198 98
Chris@30 99 int
Chris@30 100 NoteLayer::getPropertyRangeAndValue(const PropertyName &name,
Chris@216 101 int *min, int *max, int *deflt) const
Chris@30 102 {
Chris@216 103 int val = 0;
Chris@30 104
Chris@287 105 if (name == "Vertical Scale") {
Chris@30 106
Chris@30 107 if (min) *min = 0;
Chris@101 108 if (max) *max = 3;
Chris@216 109 if (deflt) *deflt = int(AutoAlignScale);
Chris@30 110
Chris@216 111 val = int(m_verticalScale);
Chris@30 112
Chris@100 113 } else if (name == "Scale Units") {
Chris@100 114
Chris@216 115 if (deflt) *deflt = 0;
Chris@100 116 if (m_model) {
Chris@216 117 val = UnitDatabase::getInstance()->getUnitId
Chris@100 118 (m_model->getScaleUnits());
Chris@100 119 }
Chris@100 120
Chris@30 121 } else {
Chris@216 122
Chris@287 123 val = SingleColourLayer::getPropertyRangeAndValue(name, min, max, deflt);
Chris@30 124 }
Chris@30 125
Chris@216 126 return val;
Chris@30 127 }
Chris@30 128
Chris@30 129 QString
Chris@30 130 NoteLayer::getPropertyValueLabel(const PropertyName &name,
Chris@287 131 int value) const
Chris@30 132 {
Chris@287 133 if (name == "Vertical Scale") {
Chris@30 134 switch (value) {
Chris@30 135 default:
Chris@101 136 case 0: return tr("Auto-Align");
Chris@198 137 case 1: return tr("Linear");
Chris@198 138 case 2: return tr("Log");
Chris@198 139 case 3: return tr("MIDI Notes");
Chris@30 140 }
Chris@30 141 }
Chris@287 142 return SingleColourLayer::getPropertyValueLabel(name, value);
Chris@30 143 }
Chris@30 144
Chris@30 145 void
Chris@30 146 NoteLayer::setProperty(const PropertyName &name, int value)
Chris@30 147 {
Chris@287 148 if (name == "Vertical Scale") {
Chris@30 149 setVerticalScale(VerticalScale(value));
Chris@100 150 } else if (name == "Scale Units") {
Chris@100 151 if (m_model) {
Chris@100 152 m_model->setScaleUnits
Chris@100 153 (UnitDatabase::getInstance()->getUnitById(value));
Chris@100 154 emit modelChanged();
Chris@100 155 }
Chris@287 156 } else {
Chris@287 157 return SingleColourLayer::setProperty(name, value);
Chris@30 158 }
Chris@30 159 }
Chris@30 160
Chris@30 161 void
Chris@30 162 NoteLayer::setVerticalScale(VerticalScale scale)
Chris@30 163 {
Chris@30 164 if (m_verticalScale == scale) return;
Chris@30 165 m_verticalScale = scale;
Chris@30 166 emit layerParametersChanged();
Chris@30 167 }
Chris@30 168
Chris@30 169 bool
Chris@44 170 NoteLayer::isLayerScrollable(const View *v) const
Chris@30 171 {
Chris@30 172 QPoint discard;
Chris@44 173 return !v->shouldIlluminateLocalFeatures(this, discard);
Chris@30 174 }
Chris@30 175
Chris@79 176 bool
Chris@101 177 NoteLayer::shouldConvertMIDIToHz() const
Chris@101 178 {
Chris@101 179 QString unit = m_model->getScaleUnits();
Chris@101 180 return (unit != "Hz");
Chris@101 181 // if (unit == "" ||
Chris@101 182 // unit.startsWith("MIDI") ||
Chris@101 183 // unit.startsWith("midi")) return true;
Chris@101 184 // return false;
Chris@101 185 }
Chris@101 186
Chris@101 187 bool
Chris@101 188 NoteLayer::getValueExtents(float &min, float &max,
Chris@101 189 bool &logarithmic, QString &unit) const
Chris@79 190 {
Chris@79 191 if (!m_model) return false;
Chris@79 192 min = m_model->getValueMinimum();
Chris@79 193 max = m_model->getValueMaximum();
Chris@101 194
Chris@105 195 if (shouldConvertMIDIToHz()) {
Chris@105 196 unit = "Hz";
Chris@105 197 min = Pitch::getFrequencyForPitch(lrintf(min));
Chris@105 198 max = Pitch::getFrequencyForPitch(lrintf(max + 1));
Chris@105 199 } else unit = m_model->getScaleUnits();
Chris@101 200
Chris@101 201 if (m_verticalScale == MIDIRangeScale ||
Chris@101 202 m_verticalScale == LogScale) logarithmic = true;
Chris@101 203
Chris@101 204 return true;
Chris@101 205 }
Chris@101 206
Chris@101 207 bool
Chris@101 208 NoteLayer::getDisplayExtents(float &min, float &max) const
Chris@101 209 {
Chris@101 210 if (!m_model || m_verticalScale == AutoAlignScale) return false;
Chris@101 211
Chris@101 212 if (m_verticalScale == MIDIRangeScale) {
Chris@101 213 min = Pitch::getFrequencyForPitch(0);
Chris@101 214 max = Pitch::getFrequencyForPitch(127);
Chris@101 215 return true;
Chris@101 216 }
Chris@101 217
Chris@101 218 min = m_model->getValueMinimum();
Chris@101 219 max = m_model->getValueMaximum();
Chris@101 220
Chris@101 221 if (shouldConvertMIDIToHz()) {
Chris@101 222 min = Pitch::getFrequencyForPitch(lrintf(min));
Chris@101 223 max = Pitch::getFrequencyForPitch(lrintf(max + 1));
Chris@101 224 }
Chris@101 225
Chris@79 226 return true;
Chris@79 227 }
Chris@79 228
Chris@30 229 NoteModel::PointList
Chris@44 230 NoteLayer::getLocalPoints(View *v, int x) const
Chris@30 231 {
Chris@30 232 if (!m_model) return NoteModel::PointList();
Chris@30 233
Chris@44 234 long frame = v->getFrameForX(x);
Chris@30 235
Chris@30 236 NoteModel::PointList onPoints =
Chris@30 237 m_model->getPoints(frame);
Chris@30 238
Chris@30 239 if (!onPoints.empty()) {
Chris@30 240 return onPoints;
Chris@30 241 }
Chris@30 242
Chris@30 243 NoteModel::PointList prevPoints =
Chris@30 244 m_model->getPreviousPoints(frame);
Chris@30 245 NoteModel::PointList nextPoints =
Chris@30 246 m_model->getNextPoints(frame);
Chris@30 247
Chris@30 248 NoteModel::PointList usePoints = prevPoints;
Chris@30 249
Chris@30 250 if (prevPoints.empty()) {
Chris@30 251 usePoints = nextPoints;
Chris@248 252 } else if (long(prevPoints.begin()->frame) < v->getStartFrame() &&
Chris@44 253 !(nextPoints.begin()->frame > v->getEndFrame())) {
Chris@30 254 usePoints = nextPoints;
Chris@248 255 } else if (long(nextPoints.begin()->frame) - frame <
Chris@248 256 frame - long(prevPoints.begin()->frame)) {
Chris@30 257 usePoints = nextPoints;
Chris@30 258 }
Chris@30 259
Chris@30 260 if (!usePoints.empty()) {
Chris@30 261 int fuzz = 2;
Chris@44 262 int px = v->getXForFrame(usePoints.begin()->frame);
Chris@30 263 if ((px > x && px - x > fuzz) ||
Chris@30 264 (px < x && x - px > fuzz + 1)) {
Chris@30 265 usePoints.clear();
Chris@30 266 }
Chris@30 267 }
Chris@30 268
Chris@30 269 return usePoints;
Chris@30 270 }
Chris@30 271
Chris@30 272 QString
Chris@44 273 NoteLayer::getFeatureDescription(View *v, QPoint &pos) const
Chris@30 274 {
Chris@30 275 int x = pos.x();
Chris@30 276
Chris@30 277 if (!m_model || !m_model->getSampleRate()) return "";
Chris@30 278
Chris@44 279 NoteModel::PointList points = getLocalPoints(v, x);
Chris@30 280
Chris@30 281 if (points.empty()) {
Chris@30 282 if (!m_model->isReady()) {
Chris@30 283 return tr("In progress");
Chris@30 284 } else {
Chris@30 285 return tr("No local points");
Chris@30 286 }
Chris@30 287 }
Chris@30 288
Chris@30 289 Note note(0);
Chris@30 290 NoteModel::PointList::iterator i;
Chris@30 291
Chris@30 292 for (i = points.begin(); i != points.end(); ++i) {
Chris@30 293
Chris@44 294 int y = getYForValue(v, i->value);
Chris@30 295 int h = 3;
Chris@30 296
Chris@30 297 if (m_model->getValueQuantization() != 0.0) {
Chris@44 298 h = y - getYForValue(v, i->value + m_model->getValueQuantization());
Chris@30 299 if (h < 3) h = 3;
Chris@30 300 }
Chris@30 301
Chris@30 302 if (pos.y() >= y - h && pos.y() <= y) {
Chris@30 303 note = *i;
Chris@30 304 break;
Chris@30 305 }
Chris@30 306 }
Chris@30 307
Chris@30 308 if (i == points.end()) return tr("No local points");
Chris@30 309
Chris@30 310 RealTime rt = RealTime::frame2RealTime(note.frame,
Chris@30 311 m_model->getSampleRate());
Chris@30 312 RealTime rd = RealTime::frame2RealTime(note.duration,
Chris@30 313 m_model->getSampleRate());
Chris@30 314
Chris@101 315 QString pitchText;
Chris@101 316
Chris@101 317 if (shouldConvertMIDIToHz()) {
Chris@101 318
Chris@101 319 int mnote = lrintf(note.value);
Chris@101 320 int cents = lrintf((note.value - mnote) * 100);
Chris@101 321 float freq = Pitch::getFrequencyForPitch(mnote, cents);
Chris@234 322 pitchText = tr("%1 (%2 Hz)")
Chris@101 323 .arg(Pitch::getPitchLabel(mnote, cents)).arg(freq);
Chris@101 324
Chris@101 325 } else if (m_model->getScaleUnits() == "Hz") {
Chris@101 326
Chris@234 327 pitchText = tr("%1 Hz (%2)")
Chris@101 328 .arg(note.value)
Chris@101 329 .arg(Pitch::getPitchLabelForFrequency(note.value));
Chris@101 330
Chris@101 331 } else {
Chris@234 332 pitchText = tr("%1 %2")
Chris@101 333 .arg(note.value).arg(m_model->getScaleUnits());
Chris@101 334 }
Chris@101 335
Chris@30 336 QString text;
Chris@30 337
Chris@30 338 if (note.label == "") {
Chris@30 339 text = QString(tr("Time:\t%1\nPitch:\t%2\nDuration:\t%3\nNo label"))
Chris@30 340 .arg(rt.toText(true).c_str())
Chris@101 341 .arg(pitchText)
Chris@30 342 .arg(rd.toText(true).c_str());
Chris@30 343 } else {
Chris@30 344 text = QString(tr("Time:\t%1\nPitch:\t%2\nDuration:\t%3\nLabel:\t%4"))
Chris@30 345 .arg(rt.toText(true).c_str())
Chris@101 346 .arg(pitchText)
Chris@30 347 .arg(rd.toText(true).c_str())
Chris@30 348 .arg(note.label);
Chris@30 349 }
Chris@30 350
Chris@44 351 pos = QPoint(v->getXForFrame(note.frame),
Chris@44 352 getYForValue(v, note.value));
Chris@30 353 return text;
Chris@30 354 }
Chris@30 355
Chris@30 356 bool
Chris@44 357 NoteLayer::snapToFeatureFrame(View *v, int &frame,
Chris@44 358 size_t &resolution,
Chris@44 359 SnapType snap) const
Chris@30 360 {
Chris@30 361 if (!m_model) {
Chris@44 362 return Layer::snapToFeatureFrame(v, frame, resolution, snap);
Chris@30 363 }
Chris@30 364
Chris@30 365 resolution = m_model->getResolution();
Chris@30 366 NoteModel::PointList points;
Chris@30 367
Chris@30 368 if (snap == SnapNeighbouring) {
Chris@30 369
Chris@44 370 points = getLocalPoints(v, v->getXForFrame(frame));
Chris@30 371 if (points.empty()) return false;
Chris@30 372 frame = points.begin()->frame;
Chris@30 373 return true;
Chris@30 374 }
Chris@30 375
Chris@30 376 points = m_model->getPoints(frame, frame);
Chris@30 377 int snapped = frame;
Chris@30 378 bool found = false;
Chris@30 379
Chris@30 380 for (NoteModel::PointList::const_iterator i = points.begin();
Chris@30 381 i != points.end(); ++i) {
Chris@30 382
Chris@30 383 if (snap == SnapRight) {
Chris@30 384
Chris@30 385 if (i->frame > frame) {
Chris@30 386 snapped = i->frame;
Chris@30 387 found = true;
Chris@30 388 break;
Chris@30 389 }
Chris@30 390
Chris@30 391 } else if (snap == SnapLeft) {
Chris@30 392
Chris@30 393 if (i->frame <= frame) {
Chris@30 394 snapped = i->frame;
Chris@30 395 found = true; // don't break, as the next may be better
Chris@30 396 } else {
Chris@30 397 break;
Chris@30 398 }
Chris@30 399
Chris@30 400 } else { // nearest
Chris@30 401
Chris@30 402 NoteModel::PointList::const_iterator j = i;
Chris@30 403 ++j;
Chris@30 404
Chris@30 405 if (j == points.end()) {
Chris@30 406
Chris@30 407 snapped = i->frame;
Chris@30 408 found = true;
Chris@30 409 break;
Chris@30 410
Chris@30 411 } else if (j->frame >= frame) {
Chris@30 412
Chris@30 413 if (j->frame - frame < frame - i->frame) {
Chris@30 414 snapped = j->frame;
Chris@30 415 } else {
Chris@30 416 snapped = i->frame;
Chris@30 417 }
Chris@30 418 found = true;
Chris@30 419 break;
Chris@30 420 }
Chris@30 421 }
Chris@30 422 }
Chris@30 423
Chris@30 424 frame = snapped;
Chris@30 425 return found;
Chris@30 426 }
Chris@30 427
Chris@101 428 void
Chris@101 429 NoteLayer::getScaleExtents(View *v, float &min, float &max, bool &log) const
Chris@30 430 {
Chris@101 431 min = 0.0;
Chris@101 432 max = 0.0;
Chris@101 433 log = false;
Chris@42 434
Chris@101 435 QString queryUnits;
Chris@101 436 if (shouldConvertMIDIToHz()) queryUnits = "Hz";
Chris@101 437 else queryUnits = m_model->getScaleUnits();
Chris@30 438
Chris@101 439 if (m_verticalScale == AutoAlignScale) {
Chris@30 440
Chris@101 441 if (!v->getValueExtents(queryUnits, min, max, log)) {
Chris@30 442
Chris@101 443 min = m_model->getValueMinimum();
Chris@101 444 max = m_model->getValueMaximum();
Chris@42 445
Chris@101 446 if (shouldConvertMIDIToHz()) {
Chris@101 447 min = Pitch::getFrequencyForPitch(lrintf(min));
Chris@101 448 max = Pitch::getFrequencyForPitch(lrintf(max + 1));
Chris@101 449 }
Chris@42 450
Chris@105 451 std::cerr << "NoteLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << std::endl;
Chris@105 452
Chris@101 453 } else if (log) {
Chris@101 454
Chris@197 455 LogRange::mapRange(min, max);
Chris@105 456
Chris@105 457 std::cerr << "NoteLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << std::endl;
Chris@105 458
Chris@101 459 }
Chris@101 460
Chris@101 461 } else {
Chris@101 462
Chris@101 463 min = m_model->getValueMinimum();
Chris@101 464 max = m_model->getValueMaximum();
Chris@101 465
Chris@101 466 if (m_verticalScale == MIDIRangeScale) {
Chris@101 467 min = Pitch::getFrequencyForPitch(0);
Chris@101 468 max = Pitch::getFrequencyForPitch(127);
Chris@101 469 } else if (shouldConvertMIDIToHz()) {
Chris@101 470 min = Pitch::getFrequencyForPitch(lrintf(min));
Chris@101 471 max = Pitch::getFrequencyForPitch(lrintf(max + 1));
Chris@101 472 }
Chris@101 473
Chris@101 474 if (m_verticalScale == LogScale || m_verticalScale == MIDIRangeScale) {
Chris@197 475 LogRange::mapRange(min, max);
Chris@101 476 log = true;
Chris@101 477 }
Chris@30 478 }
Chris@30 479
Chris@101 480 if (max == min) max = min + 1.0;
Chris@101 481 }
Chris@30 482
Chris@101 483 int
Chris@101 484 NoteLayer::getYForValue(View *v, float val) const
Chris@101 485 {
Chris@101 486 float min = 0.0, max = 0.0;
Chris@101 487 bool logarithmic = false;
Chris@101 488 int h = v->height();
Chris@101 489
Chris@101 490 getScaleExtents(v, min, max, logarithmic);
Chris@101 491
Chris@101 492 // std::cerr << "NoteLayer[" << this << "]::getYForValue(" << val << "): min = " << min << ", max = " << max << ", log = " << logarithmic << std::endl;
Chris@101 493
Chris@101 494 if (shouldConvertMIDIToHz()) {
Chris@101 495 val = Pitch::getFrequencyForPitch(lrintf(val),
Chris@101 496 lrintf((val - lrintf(val)) * 100));
Chris@101 497 // std::cerr << "shouldConvertMIDIToHz true, val now = " << val << std::endl;
Chris@101 498 }
Chris@101 499
Chris@101 500 if (logarithmic) {
Chris@197 501 val = LogRange::map(val);
Chris@101 502 // std::cerr << "logarithmic true, val now = " << val << std::endl;
Chris@101 503 }
Chris@101 504
Chris@101 505 int y = int(h - ((val - min) * h) / (max - min)) - 1;
Chris@101 506 // std::cerr << "y = " << y << std::endl;
Chris@101 507 return y;
Chris@30 508 }
Chris@30 509
Chris@30 510 float
Chris@44 511 NoteLayer::getValueForY(View *v, int y) const
Chris@30 512 {
Chris@101 513 float min = 0.0, max = 0.0;
Chris@101 514 bool logarithmic = false;
Chris@44 515 int h = v->height();
Chris@30 516
Chris@101 517 getScaleExtents(v, min, max, logarithmic);
Chris@101 518
Chris@101 519 float val = min + (float(h - y) * float(max - min)) / h;
Chris@101 520
Chris@101 521 if (logarithmic) {
Chris@197 522 val = powf(10.f, val);
Chris@101 523 }
Chris@101 524
Chris@101 525 if (shouldConvertMIDIToHz()) {
Chris@101 526 val = Pitch::getPitchForFrequency(val);
Chris@101 527 }
Chris@101 528
Chris@101 529 return val;
Chris@30 530 }
Chris@30 531
Chris@30 532 void
Chris@44 533 NoteLayer::paint(View *v, QPainter &paint, QRect rect) const
Chris@30 534 {
Chris@30 535 if (!m_model || !m_model->isOK()) return;
Chris@30 536
Chris@30 537 int sampleRate = m_model->getSampleRate();
Chris@30 538 if (!sampleRate) return;
Chris@30 539
Chris@30 540 // Profiler profiler("NoteLayer::paint", true);
Chris@30 541
Chris@30 542 int x0 = rect.left(), x1 = rect.right();
Chris@44 543 long frame0 = v->getFrameForX(x0);
Chris@44 544 long frame1 = v->getFrameForX(x1);
Chris@30 545
Chris@30 546 NoteModel::PointList points(m_model->getPoints(frame0, frame1));
Chris@30 547 if (points.empty()) return;
Chris@30 548
Chris@287 549 paint.setPen(getBaseQColor());
Chris@30 550
Chris@287 551 QColor brushColour(getBaseQColor());
Chris@30 552 brushColour.setAlpha(80);
Chris@30 553
Chris@30 554 // std::cerr << "NoteLayer::paint: resolution is "
Chris@30 555 // << m_model->getResolution() << " frames" << std::endl;
Chris@30 556
Chris@30 557 float min = m_model->getValueMinimum();
Chris@30 558 float max = m_model->getValueMaximum();
Chris@30 559 if (max == min) max = min + 1.0;
Chris@30 560
Chris@30 561 QPoint localPos;
Chris@30 562 long illuminateFrame = -1;
Chris@30 563
Chris@44 564 if (v->shouldIlluminateLocalFeatures(this, localPos)) {
Chris@30 565 NoteModel::PointList localPoints =
Chris@44 566 getLocalPoints(v, localPos.x());
Chris@30 567 if (!localPoints.empty()) illuminateFrame = localPoints.begin()->frame;
Chris@30 568 }
Chris@30 569
Chris@30 570 paint.save();
Chris@30 571 paint.setRenderHint(QPainter::Antialiasing, false);
Chris@30 572
Chris@30 573 for (NoteModel::PointList::const_iterator i = points.begin();
Chris@30 574 i != points.end(); ++i) {
Chris@30 575
Chris@30 576 const NoteModel::Point &p(*i);
Chris@30 577
Chris@44 578 int x = v->getXForFrame(p.frame);
Chris@44 579 int y = getYForValue(v, p.value);
Chris@44 580 int w = v->getXForFrame(p.frame + p.duration) - x;
Chris@30 581 int h = 3;
Chris@30 582
Chris@30 583 if (m_model->getValueQuantization() != 0.0) {
Chris@44 584 h = y - getYForValue(v, p.value + m_model->getValueQuantization());
Chris@30 585 if (h < 3) h = 3;
Chris@30 586 }
Chris@30 587
Chris@30 588 if (w < 1) w = 1;
Chris@287 589 paint.setPen(getBaseQColor());
Chris@30 590 paint.setBrush(brushColour);
Chris@30 591
Chris@30 592 if (illuminateFrame == p.frame) {
Chris@30 593 if (localPos.y() >= y - h && localPos.y() < y) {
Chris@287 594 paint.setPen(v->getForeground());
Chris@287 595 paint.setBrush(v->getForeground());
Chris@30 596 }
Chris@30 597 }
Chris@30 598
Chris@124 599 paint.drawRect(x, y - h/2, w, h);
Chris@30 600
Chris@30 601 /// if (p.label != "") {
Chris@30 602 /// paint.drawText(x + 5, y - paint.fontMetrics().height() + paint.fontMetrics().ascent(), p.label);
Chris@30 603 /// }
Chris@30 604 }
Chris@30 605
Chris@30 606 paint.restore();
Chris@30 607 }
Chris@30 608
Chris@30 609 void
Chris@44 610 NoteLayer::drawStart(View *v, QMouseEvent *e)
Chris@30 611 {
Chris@101 612 // std::cerr << "NoteLayer::drawStart(" << e->x() << "," << e->y() << ")" << std::endl;
Chris@30 613
Chris@30 614 if (!m_model) return;
Chris@30 615
Chris@44 616 long frame = v->getFrameForX(e->x());
Chris@30 617 if (frame < 0) frame = 0;
Chris@30 618 frame = frame / m_model->getResolution() * m_model->getResolution();
Chris@30 619
Chris@44 620 float value = getValueForY(v, e->y());
Chris@30 621
Chris@335 622 m_editingPoint = NoteModel::Point(frame, value, 0, 0.8, tr("New Point"));
Chris@30 623 m_originalPoint = m_editingPoint;
Chris@30 624
Chris@30 625 if (m_editingCommand) m_editingCommand->finish();
Chris@30 626 m_editingCommand = new NoteModel::EditCommand(m_model,
Chris@30 627 tr("Draw Point"));
Chris@30 628 m_editingCommand->addPoint(m_editingPoint);
Chris@30 629
Chris@30 630 m_editing = true;
Chris@30 631 }
Chris@30 632
Chris@30 633 void
Chris@44 634 NoteLayer::drawDrag(View *v, QMouseEvent *e)
Chris@30 635 {
Chris@101 636 // std::cerr << "NoteLayer::drawDrag(" << e->x() << "," << e->y() << ")" << std::endl;
Chris@30 637
Chris@30 638 if (!m_model || !m_editing) return;
Chris@30 639
Chris@44 640 long frame = v->getFrameForX(e->x());
Chris@30 641 if (frame < 0) frame = 0;
Chris@30 642 frame = frame / m_model->getResolution() * m_model->getResolution();
Chris@30 643
Chris@101 644 float newValue = getValueForY(v, e->y());
Chris@101 645
Chris@101 646 long newFrame = m_editingPoint.frame;
Chris@101 647 long newDuration = frame - newFrame;
Chris@101 648 if (newDuration < 0) {
Chris@101 649 newFrame = frame;
Chris@101 650 newDuration = -newDuration;
Chris@101 651 } else if (newDuration == 0) {
Chris@101 652 newDuration = 1;
Chris@101 653 }
Chris@30 654
Chris@30 655 m_editingCommand->deletePoint(m_editingPoint);
Chris@101 656 m_editingPoint.frame = newFrame;
Chris@101 657 m_editingPoint.value = newValue;
Chris@101 658 m_editingPoint.duration = newDuration;
Chris@30 659 m_editingCommand->addPoint(m_editingPoint);
Chris@30 660 }
Chris@30 661
Chris@30 662 void
Chris@248 663 NoteLayer::drawEnd(View *, QMouseEvent *)
Chris@30 664 {
Chris@101 665 // std::cerr << "NoteLayer::drawEnd(" << e->x() << "," << e->y() << ")" << std::endl;
Chris@30 666 if (!m_model || !m_editing) return;
Chris@30 667 m_editingCommand->finish();
Chris@30 668 m_editingCommand = 0;
Chris@30 669 m_editing = false;
Chris@30 670 }
Chris@30 671
Chris@30 672 void
Chris@335 673 NoteLayer::eraseStart(View *v, QMouseEvent *e)
Chris@335 674 {
Chris@335 675 if (!m_model) return;
Chris@335 676
Chris@335 677 NoteModel::PointList points = getLocalPoints(v, e->x());
Chris@335 678 if (points.empty()) return;
Chris@335 679
Chris@335 680 m_editingPoint = *points.begin();
Chris@335 681
Chris@335 682 if (m_editingCommand) {
Chris@335 683 m_editingCommand->finish();
Chris@335 684 m_editingCommand = 0;
Chris@335 685 }
Chris@335 686
Chris@335 687 m_editing = true;
Chris@335 688 }
Chris@335 689
Chris@335 690 void
Chris@335 691 NoteLayer::eraseDrag(View *v, QMouseEvent *e)
Chris@335 692 {
Chris@335 693 }
Chris@335 694
Chris@335 695 void
Chris@335 696 NoteLayer::eraseEnd(View *v, QMouseEvent *e)
Chris@335 697 {
Chris@335 698 if (!m_model || !m_editing) return;
Chris@335 699
Chris@335 700 m_editing = false;
Chris@335 701
Chris@335 702 NoteModel::PointList points = getLocalPoints(v, e->x());
Chris@335 703 if (points.empty()) return;
Chris@335 704 if (points.begin()->frame != m_editingPoint.frame ||
Chris@335 705 points.begin()->value != m_editingPoint.value) return;
Chris@335 706
Chris@335 707 m_editingCommand = new NoteModel::EditCommand
Chris@335 708 (m_model, tr("Erase Point"));
Chris@335 709
Chris@335 710 m_editingCommand->deletePoint(m_editingPoint);
Chris@335 711
Chris@335 712 m_editingCommand->finish();
Chris@335 713 m_editingCommand = 0;
Chris@335 714 m_editing = false;
Chris@335 715 }
Chris@335 716
Chris@335 717 void
Chris@44 718 NoteLayer::editStart(View *v, QMouseEvent *e)
Chris@30 719 {
Chris@101 720 // std::cerr << "NoteLayer::editStart(" << e->x() << "," << e->y() << ")" << std::endl;
Chris@30 721
Chris@30 722 if (!m_model) return;
Chris@30 723
Chris@44 724 NoteModel::PointList points = getLocalPoints(v, e->x());
Chris@30 725 if (points.empty()) return;
Chris@30 726
Chris@30 727 m_editingPoint = *points.begin();
Chris@30 728 m_originalPoint = m_editingPoint;
Chris@30 729
Chris@30 730 if (m_editingCommand) {
Chris@30 731 m_editingCommand->finish();
Chris@30 732 m_editingCommand = 0;
Chris@30 733 }
Chris@30 734
Chris@30 735 m_editing = true;
Chris@30 736 }
Chris@30 737
Chris@30 738 void
Chris@44 739 NoteLayer::editDrag(View *v, QMouseEvent *e)
Chris@30 740 {
Chris@101 741 // std::cerr << "NoteLayer::editDrag(" << e->x() << "," << e->y() << ")" << std::endl;
Chris@30 742
Chris@30 743 if (!m_model || !m_editing) return;
Chris@30 744
Chris@44 745 long frame = v->getFrameForX(e->x());
Chris@30 746 if (frame < 0) frame = 0;
Chris@30 747 frame = frame / m_model->getResolution() * m_model->getResolution();
Chris@30 748
Chris@44 749 float value = getValueForY(v, e->y());
Chris@30 750
Chris@30 751 if (!m_editingCommand) {
Chris@30 752 m_editingCommand = new NoteModel::EditCommand(m_model,
Chris@30 753 tr("Drag Point"));
Chris@30 754 }
Chris@30 755
Chris@30 756 m_editingCommand->deletePoint(m_editingPoint);
Chris@30 757 m_editingPoint.frame = frame;
Chris@30 758 m_editingPoint.value = value;
Chris@30 759 m_editingCommand->addPoint(m_editingPoint);
Chris@30 760 }
Chris@30 761
Chris@30 762 void
Chris@248 763 NoteLayer::editEnd(View *, QMouseEvent *)
Chris@30 764 {
Chris@101 765 // std::cerr << "NoteLayer::editEnd(" << e->x() << "," << e->y() << ")" << std::endl;
Chris@30 766 if (!m_model || !m_editing) return;
Chris@30 767
Chris@30 768 if (m_editingCommand) {
Chris@30 769
Chris@30 770 QString newName = m_editingCommand->getName();
Chris@30 771
Chris@30 772 if (m_editingPoint.frame != m_originalPoint.frame) {
Chris@30 773 if (m_editingPoint.value != m_originalPoint.value) {
Chris@30 774 newName = tr("Edit Point");
Chris@30 775 } else {
Chris@30 776 newName = tr("Relocate Point");
Chris@30 777 }
Chris@30 778 } else {
Chris@30 779 newName = tr("Change Point Value");
Chris@30 780 }
Chris@30 781
Chris@30 782 m_editingCommand->setName(newName);
Chris@30 783 m_editingCommand->finish();
Chris@30 784 }
Chris@30 785
Chris@30 786 m_editingCommand = 0;
Chris@30 787 m_editing = false;
Chris@30 788 }
Chris@30 789
Chris@255 790 bool
Chris@70 791 NoteLayer::editOpen(View *v, QMouseEvent *e)
Chris@70 792 {
Chris@255 793 if (!m_model) return false;
Chris@70 794
Chris@70 795 NoteModel::PointList points = getLocalPoints(v, e->x());
Chris@255 796 if (points.empty()) return false;
Chris@70 797
Chris@70 798 NoteModel::Point note = *points.begin();
Chris@70 799
Chris@70 800 ItemEditDialog *dialog = new ItemEditDialog
Chris@70 801 (m_model->getSampleRate(),
Chris@70 802 ItemEditDialog::ShowTime |
Chris@70 803 ItemEditDialog::ShowDuration |
Chris@70 804 ItemEditDialog::ShowValue |
Chris@100 805 ItemEditDialog::ShowText,
Chris@100 806 m_model->getScaleUnits());
Chris@70 807
Chris@70 808 dialog->setFrameTime(note.frame);
Chris@70 809 dialog->setValue(note.value);
Chris@70 810 dialog->setFrameDuration(note.duration);
Chris@70 811 dialog->setText(note.label);
Chris@70 812
Chris@70 813 if (dialog->exec() == QDialog::Accepted) {
Chris@70 814
Chris@70 815 NoteModel::Point newNote = note;
Chris@70 816 newNote.frame = dialog->getFrameTime();
Chris@70 817 newNote.value = dialog->getValue();
Chris@70 818 newNote.duration = dialog->getFrameDuration();
Chris@70 819 newNote.label = dialog->getText();
Chris@70 820
Chris@70 821 NoteModel::EditCommand *command = new NoteModel::EditCommand
Chris@70 822 (m_model, tr("Edit Point"));
Chris@70 823 command->deletePoint(note);
Chris@70 824 command->addPoint(newNote);
Chris@70 825 command->finish();
Chris@70 826 }
Chris@70 827
Chris@70 828 delete dialog;
Chris@255 829 return true;
Chris@70 830 }
Chris@70 831
Chris@70 832 void
Chris@43 833 NoteLayer::moveSelection(Selection s, size_t newStartFrame)
Chris@43 834 {
Chris@99 835 if (!m_model) return;
Chris@99 836
Chris@43 837 NoteModel::EditCommand *command =
Chris@43 838 new NoteModel::EditCommand(m_model, tr("Drag Selection"));
Chris@43 839
Chris@43 840 NoteModel::PointList points =
Chris@43 841 m_model->getPoints(s.getStartFrame(), s.getEndFrame());
Chris@43 842
Chris@43 843 for (NoteModel::PointList::iterator i = points.begin();
Chris@43 844 i != points.end(); ++i) {
Chris@43 845
Chris@43 846 if (s.contains(i->frame)) {
Chris@43 847 NoteModel::Point newPoint(*i);
Chris@43 848 newPoint.frame = i->frame + newStartFrame - s.getStartFrame();
Chris@43 849 command->deletePoint(*i);
Chris@43 850 command->addPoint(newPoint);
Chris@43 851 }
Chris@43 852 }
Chris@43 853
Chris@43 854 command->finish();
Chris@43 855 }
Chris@43 856
Chris@43 857 void
Chris@43 858 NoteLayer::resizeSelection(Selection s, Selection newSize)
Chris@43 859 {
Chris@99 860 if (!m_model) return;
Chris@99 861
Chris@43 862 NoteModel::EditCommand *command =
Chris@43 863 new NoteModel::EditCommand(m_model, tr("Resize Selection"));
Chris@43 864
Chris@43 865 NoteModel::PointList points =
Chris@43 866 m_model->getPoints(s.getStartFrame(), s.getEndFrame());
Chris@43 867
Chris@43 868 double ratio =
Chris@43 869 double(newSize.getEndFrame() - newSize.getStartFrame()) /
Chris@43 870 double(s.getEndFrame() - s.getStartFrame());
Chris@43 871
Chris@43 872 for (NoteModel::PointList::iterator i = points.begin();
Chris@43 873 i != points.end(); ++i) {
Chris@43 874
Chris@43 875 if (s.contains(i->frame)) {
Chris@43 876
Chris@43 877 double targetStart = i->frame;
Chris@43 878 targetStart = newSize.getStartFrame() +
Chris@43 879 double(targetStart - s.getStartFrame()) * ratio;
Chris@43 880
Chris@43 881 double targetEnd = i->frame + i->duration;
Chris@43 882 targetEnd = newSize.getStartFrame() +
Chris@43 883 double(targetEnd - s.getStartFrame()) * ratio;
Chris@43 884
Chris@43 885 NoteModel::Point newPoint(*i);
Chris@43 886 newPoint.frame = lrint(targetStart);
Chris@43 887 newPoint.duration = lrint(targetEnd - targetStart);
Chris@43 888 command->deletePoint(*i);
Chris@43 889 command->addPoint(newPoint);
Chris@43 890 }
Chris@43 891 }
Chris@43 892
Chris@43 893 command->finish();
Chris@43 894 }
Chris@43 895
Chris@76 896 void
Chris@76 897 NoteLayer::deleteSelection(Selection s)
Chris@76 898 {
Chris@99 899 if (!m_model) return;
Chris@99 900
Chris@76 901 NoteModel::EditCommand *command =
Chris@76 902 new NoteModel::EditCommand(m_model, tr("Delete Selected Points"));
Chris@76 903
Chris@76 904 NoteModel::PointList points =
Chris@76 905 m_model->getPoints(s.getStartFrame(), s.getEndFrame());
Chris@76 906
Chris@76 907 for (NoteModel::PointList::iterator i = points.begin();
Chris@76 908 i != points.end(); ++i) {
Chris@76 909
Chris@76 910 if (s.contains(i->frame)) {
Chris@76 911 command->deletePoint(*i);
Chris@76 912 }
Chris@76 913 }
Chris@76 914
Chris@76 915 command->finish();
Chris@76 916 }
Chris@76 917
Chris@76 918 void
Chris@76 919 NoteLayer::copy(Selection s, Clipboard &to)
Chris@76 920 {
Chris@99 921 if (!m_model) return;
Chris@99 922
Chris@76 923 NoteModel::PointList points =
Chris@76 924 m_model->getPoints(s.getStartFrame(), s.getEndFrame());
Chris@76 925
Chris@76 926 for (NoteModel::PointList::iterator i = points.begin();
Chris@76 927 i != points.end(); ++i) {
Chris@76 928 if (s.contains(i->frame)) {
Chris@335 929 Clipboard::Point point(i->frame, i->value, i->duration, i->level, i->label);
Chris@335 930 point.setReferenceFrame(m_model->alignToReference(i->frame));
Chris@76 931 to.addPoint(point);
Chris@76 932 }
Chris@76 933 }
Chris@76 934 }
Chris@76 935
Chris@125 936 bool
Chris@248 937 NoteLayer::paste(const Clipboard &from, int frameOffset, bool /* interactive */)
Chris@76 938 {
Chris@125 939 if (!m_model) return false;
Chris@99 940
Chris@76 941 const Clipboard::PointList &points = from.getPoints();
Chris@76 942
Chris@76 943 NoteModel::EditCommand *command =
Chris@76 944 new NoteModel::EditCommand(m_model, tr("Paste"));
Chris@76 945
Chris@76 946 for (Clipboard::PointList::const_iterator i = points.begin();
Chris@76 947 i != points.end(); ++i) {
Chris@76 948
Chris@76 949 if (!i->haveFrame()) continue;
Chris@76 950 size_t frame = 0;
Chris@76 951 if (frameOffset > 0 || -frameOffset < i->getFrame()) {
Chris@76 952 frame = i->getFrame() + frameOffset;
Chris@76 953 }
Chris@76 954 NoteModel::Point newPoint(frame);
Chris@76 955
Chris@76 956 if (i->haveLabel()) newPoint.label = i->getLabel();
Chris@76 957 if (i->haveValue()) newPoint.value = i->getValue();
Chris@76 958 else newPoint.value = (m_model->getValueMinimum() +
Chris@76 959 m_model->getValueMaximum()) / 2;
Chris@335 960 if (i->haveLevel()) newPoint.level = i->getLevel();
Chris@76 961 if (i->haveDuration()) newPoint.duration = i->getDuration();
Chris@125 962 else {
Chris@125 963 size_t nextFrame = frame;
Chris@125 964 Clipboard::PointList::const_iterator j = i;
Chris@125 965 for (; j != points.end(); ++j) {
Chris@125 966 if (!j->haveFrame()) continue;
Chris@125 967 if (j != i) break;
Chris@125 968 }
Chris@125 969 if (j != points.end()) {
Chris@125 970 nextFrame = j->getFrame();
Chris@125 971 }
Chris@125 972 if (nextFrame == frame) {
Chris@125 973 newPoint.duration = m_model->getResolution();
Chris@125 974 } else {
Chris@125 975 newPoint.duration = nextFrame - frame;
Chris@125 976 }
Chris@125 977 }
Chris@76 978
Chris@76 979 command->addPoint(newPoint);
Chris@76 980 }
Chris@76 981
Chris@76 982 command->finish();
Chris@125 983 return true;
Chris@76 984 }
Chris@76 985
Chris@287 986 int
Chris@287 987 NoteLayer::getDefaultColourHint(bool darkbg, bool &impose)
Chris@287 988 {
Chris@287 989 impose = false;
Chris@287 990 return ColourDatabase::getInstance()->getColourIndex
Chris@287 991 (QString(darkbg ? "White" : "Black"));
Chris@287 992 }
Chris@287 993
Chris@316 994 void
Chris@316 995 NoteLayer::toXml(QTextStream &stream,
Chris@316 996 QString indent, QString extraAttributes) const
Chris@30 997 {
Chris@316 998 SingleColourLayer::toXml(stream, indent, extraAttributes +
Chris@316 999 QString(" verticalScale=\"%1\"")
Chris@316 1000 .arg(m_verticalScale));
Chris@30 1001 }
Chris@30 1002
Chris@30 1003 void
Chris@30 1004 NoteLayer::setProperties(const QXmlAttributes &attributes)
Chris@30 1005 {
Chris@287 1006 SingleColourLayer::setProperties(attributes);
Chris@30 1007
Chris@30 1008 bool ok;
Chris@30 1009 VerticalScale scale = (VerticalScale)
Chris@30 1010 attributes.value("verticalScale").toInt(&ok);
Chris@30 1011 if (ok) setVerticalScale(scale);
Chris@30 1012 }
Chris@30 1013
Chris@30 1014