annotate layer/NoteLayer.cpp @ 392:1d85aa5a49be

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