annotate layer/TimeInstantLayer.cpp @ 359:020c485aa7e0

* More work on aligning copy/paste between layers. It's a surprisingly complicated business.
author Chris Cannam
date Wed, 06 Feb 2008 12:49:49 +0000
parents 8b69f36c74be
children d58701996fae
rev   line source
Chris@58 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@0 2
Chris@0 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@0 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@0 14 */
Chris@0 15
Chris@0 16 #include "TimeInstantLayer.h"
Chris@0 17
Chris@128 18 #include "data/model/Model.h"
Chris@0 19 #include "base/RealTime.h"
Chris@128 20 #include "view/View.h"
Chris@0 21 #include "base/Profiler.h"
Chris@76 22 #include "base/Clipboard.h"
Chris@287 23 #include "base/ColourDatabase.h"
Chris@0 24
Chris@128 25 #include "data/model/SparseOneDimensionalModel.h"
Chris@0 26
Chris@70 27 #include "widgets/ItemEditDialog.h"
Chris@358 28 #include "widgets/ListInputDialog.h"
Chris@70 29
Chris@0 30 #include <QPainter>
Chris@17 31 #include <QMouseEvent>
Chris@316 32 #include <QTextStream>
Chris@0 33
Chris@0 34 #include <iostream>
Martin@46 35 #include <cmath>
Chris@0 36
Chris@44 37 TimeInstantLayer::TimeInstantLayer() :
Chris@287 38 SingleColourLayer(),
Chris@0 39 m_model(0),
Chris@18 40 m_editing(false),
Chris@17 41 m_editingPoint(0, tr("New Point")),
Chris@22 42 m_editingCommand(0),
Chris@28 43 m_plotStyle(PlotInstants)
Chris@0 44 {
Chris@308 45 }
Chris@308 46
Chris@308 47 TimeInstantLayer::~TimeInstantLayer()
Chris@308 48 {
Chris@0 49 }
Chris@0 50
Chris@0 51 void
Chris@0 52 TimeInstantLayer::setModel(SparseOneDimensionalModel *model)
Chris@0 53 {
Chris@0 54 if (m_model == model) return;
Chris@0 55 m_model = model;
Chris@0 56
Chris@320 57 connectSignals(m_model);
Chris@0 58
Chris@0 59 std::cerr << "TimeInstantLayer::setModel(" << model << ")" << std::endl;
Chris@0 60
Chris@0 61 emit modelReplaced();
Chris@0 62 }
Chris@0 63
Chris@0 64 Layer::PropertyList
Chris@0 65 TimeInstantLayer::getProperties() const
Chris@0 66 {
Chris@287 67 PropertyList list = SingleColourLayer::getProperties();
Chris@87 68 list.push_back("Plot Type");
Chris@0 69 return list;
Chris@0 70 }
Chris@0 71
Chris@87 72 QString
Chris@87 73 TimeInstantLayer::getPropertyLabel(const PropertyName &name) const
Chris@87 74 {
Chris@87 75 if (name == "Plot Type") return tr("Plot Type");
Chris@287 76 return SingleColourLayer::getPropertyLabel(name);
Chris@87 77 }
Chris@87 78
Chris@0 79 Layer::PropertyType
Chris@287 80 TimeInstantLayer::getPropertyType(const PropertyName &name) const
Chris@0 81 {
Chris@287 82 if (name == "Plot Type") return ValueProperty;
Chris@287 83 return SingleColourLayer::getPropertyType(name);
Chris@0 84 }
Chris@0 85
Chris@0 86 int
Chris@0 87 TimeInstantLayer::getPropertyRangeAndValue(const PropertyName &name,
Chris@216 88 int *min, int *max, int *deflt) const
Chris@0 89 {
Chris@216 90 int val = 0;
Chris@0 91
Chris@287 92 if (name == "Plot Type") {
Chris@28 93
Chris@28 94 if (min) *min = 0;
Chris@28 95 if (max) *max = 1;
Chris@216 96 if (deflt) *deflt = 0;
Chris@28 97
Chris@216 98 val = int(m_plotStyle);
Chris@28 99
Chris@0 100 } else {
Chris@0 101
Chris@287 102 val = SingleColourLayer::getPropertyRangeAndValue(name, min, max, deflt);
Chris@0 103 }
Chris@0 104
Chris@216 105 return val;
Chris@0 106 }
Chris@0 107
Chris@0 108 QString
Chris@0 109 TimeInstantLayer::getPropertyValueLabel(const PropertyName &name,
Chris@287 110 int value) const
Chris@0 111 {
Chris@287 112 if (name == "Plot Type") {
Chris@28 113 switch (value) {
Chris@28 114 default:
Chris@28 115 case 0: return tr("Instants");
Chris@28 116 case 1: return tr("Segmentation");
Chris@28 117 }
Chris@0 118 }
Chris@287 119 return SingleColourLayer::getPropertyValueLabel(name, value);
Chris@0 120 }
Chris@0 121
Chris@0 122 void
Chris@0 123 TimeInstantLayer::setProperty(const PropertyName &name, int value)
Chris@0 124 {
Chris@287 125 if (name == "Plot Type") {
Chris@28 126 setPlotStyle(PlotStyle(value));
Chris@287 127 } else {
Chris@287 128 SingleColourLayer::setProperty(name, value);
Chris@0 129 }
Chris@0 130 }
Chris@0 131
Chris@0 132 void
Chris@28 133 TimeInstantLayer::setPlotStyle(PlotStyle style)
Chris@28 134 {
Chris@28 135 if (m_plotStyle == style) return;
Chris@28 136 m_plotStyle = style;
Chris@28 137 emit layerParametersChanged();
Chris@28 138 }
Chris@28 139
Chris@0 140 bool
Chris@44 141 TimeInstantLayer::isLayerScrollable(const View *v) const
Chris@0 142 {
Chris@0 143 QPoint discard;
Chris@44 144 return !v->shouldIlluminateLocalFeatures(this, discard);
Chris@0 145 }
Chris@0 146
Chris@0 147 SparseOneDimensionalModel::PointList
Chris@44 148 TimeInstantLayer::getLocalPoints(View *v, int x) const
Chris@0 149 {
Chris@28 150 // Return a set of points that all have the same frame number, the
Chris@28 151 // nearest to the given x coordinate, and that are within a
Chris@28 152 // certain fuzz distance of that x coordinate.
Chris@28 153
Chris@0 154 if (!m_model) return SparseOneDimensionalModel::PointList();
Chris@0 155
Chris@44 156 long frame = v->getFrameForX(x);
Chris@0 157
Chris@0 158 SparseOneDimensionalModel::PointList onPoints =
Chris@0 159 m_model->getPoints(frame);
Chris@0 160
Chris@0 161 if (!onPoints.empty()) {
Chris@0 162 return onPoints;
Chris@0 163 }
Chris@0 164
Chris@0 165 SparseOneDimensionalModel::PointList prevPoints =
Chris@0 166 m_model->getPreviousPoints(frame);
Chris@0 167 SparseOneDimensionalModel::PointList nextPoints =
Chris@0 168 m_model->getNextPoints(frame);
Chris@0 169
Chris@0 170 SparseOneDimensionalModel::PointList usePoints = prevPoints;
Chris@0 171
Chris@0 172 if (prevPoints.empty()) {
Chris@0 173 usePoints = nextPoints;
Chris@248 174 } else if (long(prevPoints.begin()->frame) < v->getStartFrame() &&
Chris@44 175 !(nextPoints.begin()->frame > v->getEndFrame())) {
Chris@0 176 usePoints = nextPoints;
Chris@0 177 } else if (nextPoints.begin()->frame - frame <
Chris@0 178 frame - prevPoints.begin()->frame) {
Chris@0 179 usePoints = nextPoints;
Chris@0 180 }
Chris@0 181
Chris@28 182 if (!usePoints.empty()) {
Chris@28 183 int fuzz = 2;
Chris@44 184 int px = v->getXForFrame(usePoints.begin()->frame);
Chris@28 185 if ((px > x && px - x > fuzz) ||
Chris@28 186 (px < x && x - px > fuzz + 1)) {
Chris@28 187 usePoints.clear();
Chris@28 188 }
Chris@28 189 }
Chris@28 190
Chris@0 191 return usePoints;
Chris@0 192 }
Chris@0 193
Chris@25 194 QString
Chris@44 195 TimeInstantLayer::getFeatureDescription(View *v, QPoint &pos) const
Chris@0 196 {
Chris@25 197 int x = pos.x();
Chris@0 198
Chris@25 199 if (!m_model || !m_model->getSampleRate()) return "";
Chris@0 200
Chris@44 201 SparseOneDimensionalModel::PointList points = getLocalPoints(v, x);
Chris@0 202
Chris@0 203 if (points.empty()) {
Chris@0 204 if (!m_model->isReady()) {
Chris@25 205 return tr("In progress");
Chris@25 206 } else {
Chris@25 207 return tr("No local points");
Chris@0 208 }
Chris@0 209 }
Chris@0 210
Chris@0 211 long useFrame = points.begin()->frame;
Chris@0 212
Chris@0 213 RealTime rt = RealTime::frame2RealTime(useFrame, m_model->getSampleRate());
Chris@25 214
Chris@25 215 QString text;
Chris@0 216
Chris@25 217 if (points.begin()->label == "") {
Chris@25 218 text = QString(tr("Time:\t%1\nNo label"))
Chris@25 219 .arg(rt.toText(true).c_str());
Chris@25 220 } else {
Chris@25 221 text = QString(tr("Time:\t%1\nLabel:\t%2"))
Chris@25 222 .arg(rt.toText(true).c_str())
Chris@25 223 .arg(points.begin()->label);
Chris@25 224 }
Chris@0 225
Chris@44 226 pos = QPoint(v->getXForFrame(useFrame), pos.y());
Chris@25 227 return text;
Chris@0 228 }
Chris@0 229
Chris@28 230 bool
Chris@44 231 TimeInstantLayer::snapToFeatureFrame(View *v, int &frame,
Chris@28 232 size_t &resolution,
Chris@28 233 SnapType snap) const
Chris@13 234 {
Chris@13 235 if (!m_model) {
Chris@44 236 return Layer::snapToFeatureFrame(v, frame, resolution, snap);
Chris@13 237 }
Chris@13 238
Chris@13 239 resolution = m_model->getResolution();
Chris@28 240 SparseOneDimensionalModel::PointList points;
Chris@13 241
Chris@28 242 if (snap == SnapNeighbouring) {
Chris@28 243
Chris@44 244 points = getLocalPoints(v, v->getXForFrame(frame));
Chris@28 245 if (points.empty()) return false;
Chris@28 246 frame = points.begin()->frame;
Chris@28 247 return true;
Chris@28 248 }
Chris@28 249
Chris@28 250 points = m_model->getPoints(frame, frame);
Chris@28 251 int snapped = frame;
Chris@28 252 bool found = false;
Chris@13 253
Chris@13 254 for (SparseOneDimensionalModel::PointList::const_iterator i = points.begin();
Chris@13 255 i != points.end(); ++i) {
Chris@13 256
Chris@28 257 if (snap == SnapRight) {
Chris@28 258
Chris@28 259 if (i->frame >= frame) {
Chris@28 260 snapped = i->frame;
Chris@28 261 found = true;
Chris@13 262 break;
Chris@13 263 }
Chris@28 264
Chris@28 265 } else if (snap == SnapLeft) {
Chris@28 266
Chris@13 267 if (i->frame <= frame) {
Chris@28 268 snapped = i->frame;
Chris@28 269 found = true; // don't break, as the next may be better
Chris@28 270 } else {
Chris@28 271 break;
Chris@28 272 }
Chris@28 273
Chris@28 274 } else { // nearest
Chris@28 275
Chris@28 276 SparseOneDimensionalModel::PointList::const_iterator j = i;
Chris@28 277 ++j;
Chris@28 278
Chris@28 279 if (j == points.end()) {
Chris@28 280
Chris@28 281 snapped = i->frame;
Chris@28 282 found = true;
Chris@28 283 break;
Chris@28 284
Chris@28 285 } else if (j->frame >= frame) {
Chris@28 286
Chris@28 287 if (j->frame - frame < frame - i->frame) {
Chris@28 288 snapped = j->frame;
Chris@28 289 } else {
Chris@28 290 snapped = i->frame;
Chris@28 291 }
Chris@28 292 found = true;
Chris@28 293 break;
Chris@13 294 }
Chris@13 295 }
Chris@13 296 }
Chris@13 297
Chris@28 298 frame = snapped;
Chris@28 299 return found;
Chris@13 300 }
Chris@13 301
Chris@0 302 void
Chris@44 303 TimeInstantLayer::paint(View *v, QPainter &paint, QRect rect) const
Chris@0 304 {
Chris@0 305 if (!m_model || !m_model->isOK()) return;
Chris@0 306
Chris@0 307 // Profiler profiler("TimeInstantLayer::paint", true);
Chris@0 308
Chris@20 309 int x0 = rect.left(), x1 = rect.right();
Chris@0 310
Chris@44 311 long frame0 = v->getFrameForX(x0);
Chris@44 312 long frame1 = v->getFrameForX(x1);
Chris@0 313
Chris@0 314 SparseOneDimensionalModel::PointList points(m_model->getPoints
Chris@0 315 (frame0, frame1));
Chris@0 316
Chris@28 317 bool odd = false;
Chris@28 318 if (m_plotStyle == PlotSegmentation && !points.empty()) {
Chris@28 319 int index = m_model->getIndexOf(*points.begin());
Chris@28 320 odd = ((index % 2) == 1);
Chris@28 321 }
Chris@28 322
Chris@287 323 paint.setPen(getBaseQColor());
Chris@0 324
Chris@287 325 QColor brushColour(getBaseQColor());
Chris@0 326 brushColour.setAlpha(100);
Chris@0 327 paint.setBrush(brushColour);
Chris@0 328
Chris@28 329 QColor oddBrushColour(brushColour);
Chris@28 330 if (m_plotStyle == PlotSegmentation) {
Chris@287 331 if (getBaseQColor() == Qt::black) {
Chris@28 332 oddBrushColour = Qt::gray;
Chris@287 333 } else if (getBaseQColor() == Qt::darkRed) {
Chris@28 334 oddBrushColour = Qt::red;
Chris@287 335 } else if (getBaseQColor() == Qt::darkBlue) {
Chris@28 336 oddBrushColour = Qt::blue;
Chris@287 337 } else if (getBaseQColor() == Qt::darkGreen) {
Chris@28 338 oddBrushColour = Qt::green;
Chris@28 339 } else {
Chris@28 340 oddBrushColour = oddBrushColour.light(150);
Chris@28 341 }
Chris@28 342 oddBrushColour.setAlpha(100);
Chris@28 343 }
Chris@28 344
Chris@0 345 // std::cerr << "TimeInstantLayer::paint: resolution is "
Chris@0 346 // << m_model->getResolution() << " frames" << std::endl;
Chris@0 347
Chris@0 348 QPoint localPos;
Chris@0 349 long illuminateFrame = -1;
Chris@0 350
Chris@44 351 if (v->shouldIlluminateLocalFeatures(this, localPos)) {
Chris@0 352 SparseOneDimensionalModel::PointList localPoints =
Chris@44 353 getLocalPoints(v, localPos.x());
Chris@0 354 if (!localPoints.empty()) illuminateFrame = localPoints.begin()->frame;
Chris@0 355 }
Chris@0 356
Chris@23 357 int prevX = -1;
Chris@79 358 int textY = v->getTextLabelHeight(this, paint);
Chris@79 359
Chris@0 360 for (SparseOneDimensionalModel::PointList::const_iterator i = points.begin();
Chris@0 361 i != points.end(); ++i) {
Chris@0 362
Chris@0 363 const SparseOneDimensionalModel::Point &p(*i);
Chris@17 364 SparseOneDimensionalModel::PointList::const_iterator j = i;
Chris@17 365 ++j;
Chris@0 366
Chris@44 367 int x = v->getXForFrame(p.frame);
Chris@23 368 if (x == prevX && p.frame != illuminateFrame) continue;
Chris@23 369
Chris@44 370 int iw = v->getXForFrame(p.frame + m_model->getResolution()) - x;
Chris@16 371 if (iw < 2) {
Chris@17 372 if (iw < 1) {
Chris@17 373 iw = 2;
Chris@17 374 if (j != points.end()) {
Chris@44 375 int nx = v->getXForFrame(j->frame);
Chris@17 376 if (nx < x + 3) iw = 1;
Chris@17 377 }
Chris@17 378 } else {
Chris@17 379 iw = 2;
Chris@17 380 }
Chris@16 381 }
Chris@20 382
Chris@0 383 if (p.frame == illuminateFrame) {
Chris@287 384 paint.setPen(getForegroundQColor(v));
Chris@0 385 } else {
Chris@0 386 paint.setPen(brushColour);
Chris@0 387 }
Chris@23 388
Chris@28 389 if (m_plotStyle == PlotInstants) {
Chris@28 390 if (iw > 1) {
Chris@44 391 paint.drawRect(x, 0, iw - 1, v->height() - 1);
Chris@28 392 } else {
Chris@44 393 paint.drawLine(x, 0, x, v->height() - 1);
Chris@28 394 }
Chris@23 395 } else {
Chris@28 396
Chris@28 397 if (odd) paint.setBrush(oddBrushColour);
Chris@28 398 else paint.setBrush(brushColour);
Chris@28 399
Chris@28 400 int nx;
Chris@28 401
Chris@28 402 if (j != points.end()) {
Chris@28 403 const SparseOneDimensionalModel::Point &q(*j);
Chris@44 404 nx = v->getXForFrame(q.frame);
Chris@28 405 } else {
Chris@44 406 nx = v->getXForFrame(m_model->getEndFrame());
Chris@28 407 }
Chris@28 408
Chris@28 409 if (nx >= x) {
Chris@28 410
Chris@28 411 if (illuminateFrame != p.frame &&
Chris@44 412 (nx < x + 5 || x >= v->width() - 1)) {
Chris@28 413 paint.setPen(Qt::NoPen);
Chris@28 414 }
Chris@28 415
Chris@44 416 paint.drawRect(x, -1, nx - x, v->height() + 1);
Chris@28 417 }
Chris@28 418
Chris@28 419 odd = !odd;
Chris@23 420 }
Chris@28 421
Chris@287 422 paint.setPen(getBaseQColor());
Chris@0 423
Chris@0 424 if (p.label != "") {
Chris@0 425
Chris@0 426 // only draw if there's enough room from here to the next point
Chris@0 427
Chris@0 428 int lw = paint.fontMetrics().width(p.label);
Chris@0 429 bool good = true;
Chris@0 430
Chris@17 431 if (j != points.end()) {
Chris@44 432 int nx = v->getXForFrame(j->frame);
Chris@20 433 if (nx >= x && nx - x - iw - 3 <= lw) good = false;
Chris@0 434 }
Chris@0 435
Chris@0 436 if (good) {
Chris@79 437 paint.drawText(x + iw + 2, textY, p.label);
Chris@0 438 }
Chris@0 439 }
Chris@23 440
Chris@23 441 prevX = x;
Chris@0 442 }
Chris@0 443 }
Chris@0 444
Chris@17 445 void
Chris@44 446 TimeInstantLayer::drawStart(View *v, QMouseEvent *e)
Chris@17 447 {
Chris@17 448 std::cerr << "TimeInstantLayer::drawStart(" << e->x() << ")" << std::endl;
Chris@17 449
Chris@17 450 if (!m_model) return;
Chris@17 451
Chris@44 452 long frame = v->getFrameForX(e->x());
Chris@17 453 if (frame < 0) frame = 0;
Chris@21 454 frame = frame / m_model->getResolution() * m_model->getResolution();
Chris@22 455
Chris@17 456 m_editingPoint = SparseOneDimensionalModel::Point(frame, tr("New Point"));
Chris@22 457
Chris@22 458 if (m_editingCommand) m_editingCommand->finish();
Chris@22 459 m_editingCommand = new SparseOneDimensionalModel::EditCommand(m_model,
Chris@22 460 tr("Draw Point"));
Chris@22 461 m_editingCommand->addPoint(m_editingPoint);
Chris@22 462
Chris@18 463 m_editing = true;
Chris@17 464 }
Chris@17 465
Chris@17 466 void
Chris@44 467 TimeInstantLayer::drawDrag(View *v, QMouseEvent *e)
Chris@17 468 {
Chris@17 469 std::cerr << "TimeInstantLayer::drawDrag(" << e->x() << ")" << std::endl;
Chris@17 470
Chris@18 471 if (!m_model || !m_editing) return;
Chris@17 472
Chris@44 473 long frame = v->getFrameForX(e->x());
Chris@17 474 if (frame < 0) frame = 0;
Chris@21 475 frame = frame / m_model->getResolution() * m_model->getResolution();
Chris@22 476 m_editingCommand->deletePoint(m_editingPoint);
Chris@17 477 m_editingPoint.frame = frame;
Chris@22 478 m_editingCommand->addPoint(m_editingPoint);
Chris@17 479 }
Chris@17 480
Chris@17 481 void
Chris@248 482 TimeInstantLayer::drawEnd(View *, QMouseEvent *e)
Chris@17 483 {
Chris@17 484 std::cerr << "TimeInstantLayer::drawEnd(" << e->x() << ")" << std::endl;
Chris@18 485 if (!m_model || !m_editing) return;
Chris@23 486 QString newName = tr("Add Point at %1 s")
Chris@23 487 .arg(RealTime::frame2RealTime(m_editingPoint.frame,
Chris@23 488 m_model->getSampleRate())
Chris@23 489 .toText(false).c_str());
Chris@23 490 m_editingCommand->setName(newName);
Chris@22 491 m_editingCommand->finish();
Chris@22 492 m_editingCommand = 0;
Chris@18 493 m_editing = false;
Chris@18 494 }
Chris@18 495
Chris@18 496 void
Chris@335 497 TimeInstantLayer::eraseStart(View *v, QMouseEvent *e)
Chris@335 498 {
Chris@335 499 if (!m_model) return;
Chris@335 500
Chris@335 501 SparseOneDimensionalModel::PointList points = getLocalPoints(v, e->x());
Chris@335 502 if (points.empty()) return;
Chris@335 503
Chris@335 504 m_editingPoint = *points.begin();
Chris@335 505
Chris@335 506 if (m_editingCommand) {
Chris@335 507 m_editingCommand->finish();
Chris@335 508 m_editingCommand = 0;
Chris@335 509 }
Chris@335 510
Chris@335 511 m_editing = true;
Chris@335 512 }
Chris@335 513
Chris@335 514 void
Chris@335 515 TimeInstantLayer::eraseDrag(View *v, QMouseEvent *e)
Chris@335 516 {
Chris@335 517 }
Chris@335 518
Chris@335 519 void
Chris@335 520 TimeInstantLayer::eraseEnd(View *v, QMouseEvent *e)
Chris@335 521 {
Chris@335 522 if (!m_model || !m_editing) return;
Chris@335 523
Chris@335 524 m_editing = false;
Chris@335 525
Chris@335 526 SparseOneDimensionalModel::PointList points = getLocalPoints(v, e->x());
Chris@335 527 if (points.empty()) return;
Chris@335 528 if (points.begin()->frame != m_editingPoint.frame) return;
Chris@335 529
Chris@335 530 m_editingCommand = new SparseOneDimensionalModel::EditCommand
Chris@335 531 (m_model, tr("Erase Point"));
Chris@335 532
Chris@335 533 m_editingCommand->deletePoint(m_editingPoint);
Chris@335 534
Chris@335 535 m_editingCommand->finish();
Chris@335 536 m_editingCommand = 0;
Chris@335 537 m_editing = false;
Chris@335 538 }
Chris@335 539
Chris@335 540 void
Chris@44 541 TimeInstantLayer::editStart(View *v, QMouseEvent *e)
Chris@18 542 {
Chris@18 543 std::cerr << "TimeInstantLayer::editStart(" << e->x() << ")" << std::endl;
Chris@18 544
Chris@17 545 if (!m_model) return;
Chris@18 546
Chris@44 547 SparseOneDimensionalModel::PointList points = getLocalPoints(v, e->x());
Chris@18 548 if (points.empty()) return;
Chris@18 549
Chris@18 550 m_editingPoint = *points.begin();
Chris@22 551
Chris@22 552 if (m_editingCommand) {
Chris@22 553 m_editingCommand->finish();
Chris@22 554 m_editingCommand = 0;
Chris@22 555 }
Chris@22 556
Chris@18 557 m_editing = true;
Chris@18 558 }
Chris@18 559
Chris@18 560 void
Chris@44 561 TimeInstantLayer::editDrag(View *v, QMouseEvent *e)
Chris@18 562 {
Chris@18 563 std::cerr << "TimeInstantLayer::editDrag(" << e->x() << ")" << std::endl;
Chris@18 564
Chris@18 565 if (!m_model || !m_editing) return;
Chris@18 566
Chris@44 567 long frame = v->getFrameForX(e->x());
Chris@18 568 if (frame < 0) frame = 0;
Chris@21 569 frame = frame / m_model->getResolution() * m_model->getResolution();
Chris@22 570
Chris@22 571 if (!m_editingCommand) {
Chris@22 572 m_editingCommand = new SparseOneDimensionalModel::EditCommand(m_model,
Chris@22 573 tr("Drag Point"));
Chris@22 574 }
Chris@22 575
Chris@22 576 m_editingCommand->deletePoint(m_editingPoint);
Chris@18 577 m_editingPoint.frame = frame;
Chris@22 578 m_editingCommand->addPoint(m_editingPoint);
Chris@18 579 }
Chris@18 580
Chris@18 581 void
Chris@248 582 TimeInstantLayer::editEnd(View *, QMouseEvent *e)
Chris@18 583 {
Chris@18 584 std::cerr << "TimeInstantLayer::editEnd(" << e->x() << ")" << std::endl;
Chris@18 585 if (!m_model || !m_editing) return;
Chris@23 586 if (m_editingCommand) {
Chris@23 587 QString newName = tr("Move Point to %1 s")
Chris@23 588 .arg(RealTime::frame2RealTime(m_editingPoint.frame,
Chris@23 589 m_model->getSampleRate())
Chris@23 590 .toText(false).c_str());
Chris@23 591 m_editingCommand->setName(newName);
Chris@23 592 m_editingCommand->finish();
Chris@23 593 }
Chris@22 594 m_editingCommand = 0;
Chris@18 595 m_editing = false;
Chris@17 596 }
Chris@17 597
Chris@255 598 bool
Chris@70 599 TimeInstantLayer::editOpen(View *v, QMouseEvent *e)
Chris@70 600 {
Chris@255 601 if (!m_model) return false;
Chris@70 602
Chris@70 603 SparseOneDimensionalModel::PointList points = getLocalPoints(v, e->x());
Chris@255 604 if (points.empty()) return false;
Chris@70 605
Chris@70 606 SparseOneDimensionalModel::Point point = *points.begin();
Chris@70 607
Chris@70 608 ItemEditDialog *dialog = new ItemEditDialog
Chris@70 609 (m_model->getSampleRate(),
Chris@70 610 ItemEditDialog::ShowTime |
Chris@70 611 ItemEditDialog::ShowText);
Chris@70 612
Chris@70 613 dialog->setFrameTime(point.frame);
Chris@70 614 dialog->setText(point.label);
Chris@70 615
Chris@70 616 if (dialog->exec() == QDialog::Accepted) {
Chris@70 617
Chris@70 618 SparseOneDimensionalModel::Point newPoint = point;
Chris@70 619 newPoint.frame = dialog->getFrameTime();
Chris@70 620 newPoint.label = dialog->getText();
Chris@70 621
Chris@70 622 SparseOneDimensionalModel::EditCommand *command =
Chris@70 623 new SparseOneDimensionalModel::EditCommand(m_model, tr("Edit Point"));
Chris@70 624 command->deletePoint(point);
Chris@70 625 command->addPoint(newPoint);
Chris@70 626 command->finish();
Chris@70 627 }
Chris@70 628
Chris@70 629 delete dialog;
Chris@255 630 return true;
Chris@70 631 }
Chris@70 632
Chris@70 633 void
Chris@43 634 TimeInstantLayer::moveSelection(Selection s, size_t newStartFrame)
Chris@43 635 {
Chris@99 636 if (!m_model) return;
Chris@99 637
Chris@43 638 SparseOneDimensionalModel::EditCommand *command =
Chris@43 639 new SparseOneDimensionalModel::EditCommand(m_model,
Chris@43 640 tr("Drag Selection"));
Chris@43 641
Chris@43 642 SparseOneDimensionalModel::PointList points =
Chris@43 643 m_model->getPoints(s.getStartFrame(), s.getEndFrame());
Chris@43 644
Chris@43 645 for (SparseOneDimensionalModel::PointList::iterator i = points.begin();
Chris@43 646 i != points.end(); ++i) {
Chris@43 647
Chris@43 648 if (s.contains(i->frame)) {
Chris@43 649 SparseOneDimensionalModel::Point newPoint(*i);
Chris@43 650 newPoint.frame = i->frame + newStartFrame - s.getStartFrame();
Chris@43 651 command->deletePoint(*i);
Chris@43 652 command->addPoint(newPoint);
Chris@43 653 }
Chris@43 654 }
Chris@43 655
Chris@43 656 command->finish();
Chris@43 657 }
Chris@43 658
Chris@43 659 void
Chris@43 660 TimeInstantLayer::resizeSelection(Selection s, Selection newSize)
Chris@43 661 {
Chris@99 662 if (!m_model) return;
Chris@99 663
Chris@43 664 SparseOneDimensionalModel::EditCommand *command =
Chris@43 665 new SparseOneDimensionalModel::EditCommand(m_model,
Chris@43 666 tr("Resize Selection"));
Chris@43 667
Chris@43 668 SparseOneDimensionalModel::PointList points =
Chris@43 669 m_model->getPoints(s.getStartFrame(), s.getEndFrame());
Chris@43 670
Chris@43 671 double ratio =
Chris@43 672 double(newSize.getEndFrame() - newSize.getStartFrame()) /
Chris@43 673 double(s.getEndFrame() - s.getStartFrame());
Chris@43 674
Chris@43 675 for (SparseOneDimensionalModel::PointList::iterator i = points.begin();
Chris@43 676 i != points.end(); ++i) {
Chris@43 677
Chris@43 678 if (s.contains(i->frame)) {
Chris@43 679
Chris@43 680 double target = i->frame;
Chris@43 681 target = newSize.getStartFrame() +
Chris@43 682 double(target - s.getStartFrame()) * ratio;
Chris@43 683
Chris@43 684 SparseOneDimensionalModel::Point newPoint(*i);
Chris@43 685 newPoint.frame = lrint(target);
Chris@43 686 command->deletePoint(*i);
Chris@43 687 command->addPoint(newPoint);
Chris@43 688 }
Chris@43 689 }
Chris@43 690
Chris@43 691 command->finish();
Chris@43 692 }
Chris@43 693
Chris@43 694 void
Chris@43 695 TimeInstantLayer::deleteSelection(Selection s)
Chris@43 696 {
Chris@99 697 if (!m_model) return;
Chris@99 698
Chris@43 699 SparseOneDimensionalModel::EditCommand *command =
Chris@43 700 new SparseOneDimensionalModel::EditCommand(m_model,
Chris@43 701 tr("Delete Selection"));
Chris@43 702
Chris@43 703 SparseOneDimensionalModel::PointList points =
Chris@43 704 m_model->getPoints(s.getStartFrame(), s.getEndFrame());
Chris@43 705
Chris@43 706 for (SparseOneDimensionalModel::PointList::iterator i = points.begin();
Chris@43 707 i != points.end(); ++i) {
Chris@43 708 if (s.contains(i->frame)) command->deletePoint(*i);
Chris@43 709 }
Chris@43 710
Chris@43 711 command->finish();
Chris@43 712 }
Chris@76 713
Chris@76 714 void
Chris@359 715 TimeInstantLayer::copy(View *v, Selection s, Clipboard &to)
Chris@76 716 {
Chris@99 717 if (!m_model) return;
Chris@99 718
Chris@76 719 SparseOneDimensionalModel::PointList points =
Chris@76 720 m_model->getPoints(s.getStartFrame(), s.getEndFrame());
Chris@76 721
Chris@76 722 for (SparseOneDimensionalModel::PointList::iterator i = points.begin();
Chris@76 723 i != points.end(); ++i) {
Chris@76 724 if (s.contains(i->frame)) {
Chris@76 725 Clipboard::Point point(i->frame, i->label);
Chris@358 726
Chris@358 727 //!!! This fails, because simply being "on the same pane as" a
Chris@358 728 // particular model is not enough to give this layer the same
Chris@358 729 // alignment as it. If it was generated by deriving from another
Chris@358 730 // layer's model, that would be... but it wasn't necessarily
Chris@358 731
Chris@359 732 point.setReferenceFrame(alignToReference(v, i->frame));
Chris@359 733
Chris@359 734 std::cerr << "TimeInstantLayer::copy: frame = " << i->frame << ", reference frame = " << point.getReferenceFrame() << std::endl;
Chris@359 735
Chris@76 736 to.addPoint(point);
Chris@76 737 }
Chris@76 738 }
Chris@76 739 }
Chris@76 740
Chris@125 741 bool
Chris@359 742 TimeInstantLayer::clipboardAlignmentDiffers(View *v, const Clipboard &clip) const
Chris@358 743 {
Chris@358 744 //!!! hoist -- all pastable layers will need this
Chris@358 745
Chris@358 746 //!!! This fails, because simply being "on the same pane as" a
Chris@358 747 // particular model is not enough to give this layer the same
Chris@358 748 // alignment as it. If it was generated by deriving from another
Chris@358 749 // layer's model, that would be... but it wasn't necessarily
Chris@358 750
Chris@358 751 if (!m_model) return false;
Chris@358 752
Chris@358 753 std::cerr << "TimeInstantLayer::clipboardAlignmentDiffers" << std::endl;
Chris@358 754
Chris@358 755 for (Clipboard::PointList::const_iterator i = clip.getPoints().begin();
Chris@358 756 i != clip.getPoints().end(); ++i) {
Chris@358 757
Chris@358 758 // In principle, we want to know whether the aligned version
Chris@358 759 // of the reference frame in our model is the same as the
Chris@358 760 // source frame contained in the clipboard point. However,
Chris@358 761 // because of rounding during alignment, that won't
Chris@358 762 // necessarily be the case even if the clipboard point came
Chris@358 763 // from our model! What we need to check is whether, if we
Chris@358 764 // aligned the clipboard point's frame back to the reference
Chris@358 765 // using this model's alignment, we would obtain the same
Chris@358 766 // reference frame as that for the clipboard point.
Chris@358 767
Chris@358 768 // What if the clipboard point has no reference frame? Then
Chris@358 769 // we have to treat it as having its own frame as the
Chris@358 770 // reference (i.e. having been copied from the reference
Chris@358 771 // model).
Chris@358 772
Chris@358 773 long sourceFrame = i->getFrame();
Chris@358 774 long referenceFrame = sourceFrame;
Chris@358 775 if (i->haveReferenceFrame()) {
Chris@358 776 referenceFrame = i->getReferenceFrame();
Chris@358 777 }
Chris@359 778 long myMappedFrame = alignToReference(v, sourceFrame);
Chris@358 779
Chris@358 780 std::cerr << "sourceFrame = " << sourceFrame << ", referenceFrame = " << referenceFrame << " (have = " << i->haveReferenceFrame() << "), myMappedFrame = " << myMappedFrame << std::endl;
Chris@358 781
Chris@358 782 if (myMappedFrame != referenceFrame) return true;
Chris@358 783 }
Chris@358 784
Chris@358 785 return false;
Chris@358 786 }
Chris@358 787
Chris@358 788 bool
Chris@359 789 TimeInstantLayer::paste(View *v, const Clipboard &from, int frameOffset, bool)
Chris@76 790 {
Chris@125 791 if (!m_model) return false;
Chris@99 792
Chris@76 793 const Clipboard::PointList &points = from.getPoints();
Chris@76 794
Chris@335 795 //!!!
Chris@335 796
Chris@335 797 // Clipboard::haveReferenceFrames() will return true if any of the
Chris@335 798 // items in the clipboard came from an aligned, non-reference model.
Chris@335 799
Chris@335 800 // We need to know whether these points came from our model or not
Chris@335 801 // -- if they did, we don't want to align them.
Chris@335 802
Chris@335 803 // If they didn't come from our model, and if reference frames are
Chris@335 804 // available, then we want to offer to align them. If reference
Chris@335 805 // frames are unavailable but they came from the reference model,
Chris@335 806 // we want to offer to align them too.
Chris@335 807
Chris@356 808
Chris@356 809 //!!!
Chris@356 810
Chris@356 811 // Each point may have a reference frame that may differ from the
Chris@356 812 // point's given frame (in its source model). If it has no
Chris@356 813 // reference frame, we have to assume the source model was not
Chris@356 814 // aligned or was the reference model: when cutting or copying
Chris@356 815 // points from a layer, we must always set their reference frame
Chris@356 816 // correctly if we are aligned.
Chris@356 817 //
Chris@356 818 // When pasting:
Chris@356 819 // - if point's reference and aligned frames differ:
Chris@356 820 // - if this layer is aligned:
Chris@356 821 // - if point's aligned frame matches this layer's aligned version
Chris@356 822 // of point's reference frame:
Chris@356 823 // - we can paste at reference frame or our frame
Chris@356 824 // - else
Chris@356 825 // - we can paste at reference frame, result of aligning reference
Chris@356 826 // frame in our model, or literal source frame
Chris@356 827 // - else
Chris@356 828 // - we can paste at reference (our) frame, or literal source frame
Chris@356 829 // - else
Chris@356 830 // - if this layer is aligned:
Chris@356 831 // - we can paste at reference (point's only available) frame,
Chris@356 832 // or result of aligning reference frame in our model
Chris@356 833 // - else
Chris@356 834 // - we can only paste at reference frame
Chris@356 835 //
Chris@356 836 // Which of these alternatives are useful?
Chris@356 837 //
Chris@356 838 // Example: we paste between two tracks that are aligned to the
Chris@356 839 // same reference, and the points are at 10s and 20s in the source
Chris@356 840 // track, corresponding to 5s and 10s in the reference but 20s and
Chris@356 841 // 30s in the target track.
Chris@356 842 //
Chris@356 843 // The obvious default is to paste at 20s and 30s; if we aren't
Chris@356 844 // doing that, would it be better to paste at 5s and 10s or at 10s
Chris@356 845 // and 20s? We probably don't ever want to do the former, do we?
Chris@356 846 // We either want to be literal all the way through, or aligned
Chris@356 847 // all the way through.
Chris@356 848
Chris@358 849 bool realign = false;
Chris@358 850
Chris@359 851 if (clipboardAlignmentDiffers(v, from)) {
Chris@358 852
Chris@358 853 std::cerr << "Offer alignment option..." << std::endl;
Chris@358 854
Chris@358 855 QStringList options;
Chris@358 856 options << "Use times unchanged from the original layer";
Chris@358 857 options << "Re-align times to match the same points in the reference layer";
Chris@358 858
Chris@358 859 bool ok = false;
Chris@358 860
Chris@358 861 QString selected = ListInputDialog::getItem
Chris@358 862 (0, tr("Choose alignment"),
Chris@358 863 tr("The points you are pasting originated in a layer with different alignment from the current layer. Would you like to re-align them when pasting?"),
Chris@358 864 options, 0, &ok);
Chris@358 865 if (!ok) return false;
Chris@358 866
Chris@358 867 if (selected == options[1]) realign = true;
Chris@358 868 }
Chris@358 869
Chris@358 870 SparseOneDimensionalModel::EditCommand *command =
Chris@358 871 new SparseOneDimensionalModel::EditCommand(m_model, tr("Paste"));
Chris@358 872
Chris@76 873 for (Clipboard::PointList::const_iterator i = points.begin();
Chris@76 874 i != points.end(); ++i) {
Chris@76 875
Chris@76 876 if (!i->haveFrame()) continue;
Chris@359 877
Chris@76 878 size_t frame = 0;
Chris@359 879
Chris@359 880 if (!realign) {
Chris@359 881
Chris@359 882 frame = i->getFrame();
Chris@359 883
Chris@359 884 } else {
Chris@359 885
Chris@359 886 if (i->haveReferenceFrame()) {
Chris@359 887 frame = i->getReferenceFrame();
Chris@359 888 frame = alignFromReference(v, frame);
Chris@359 889 } else {
Chris@359 890 frame = i->getFrame();
Chris@359 891 }
Chris@76 892 }
Chris@359 893
Chris@359 894 if (frameOffset > 0) frame += frameOffset;
Chris@359 895 else if (frameOffset < 0) {
Chris@359 896 if (frame > -frameOffset) frame += frameOffset;
Chris@359 897 else frame = 0;
Chris@359 898 }
Chris@359 899
Chris@76 900 SparseOneDimensionalModel::Point newPoint(frame);
Chris@125 901 if (i->haveLabel()) {
Chris@125 902 newPoint.label = i->getLabel();
Chris@125 903 } else if (i->haveValue()) {
Chris@125 904 newPoint.label = QString("%1").arg(i->getValue());
Chris@125 905 }
Chris@76 906
Chris@76 907 command->addPoint(newPoint);
Chris@76 908 }
Chris@76 909
Chris@76 910 command->finish();
Chris@125 911 return true;
Chris@76 912 }
Chris@43 913
Chris@287 914 int
Chris@287 915 TimeInstantLayer::getDefaultColourHint(bool darkbg, bool &impose)
Chris@287 916 {
Chris@287 917 impose = false;
Chris@287 918 return ColourDatabase::getInstance()->getColourIndex
Chris@287 919 (QString(darkbg ? "Bright Purple" : "Purple"));
Chris@287 920 }
Chris@287 921
Chris@316 922 void
Chris@316 923 TimeInstantLayer::toXml(QTextStream &stream,
Chris@316 924 QString indent, QString extraAttributes) const
Chris@6 925 {
Chris@316 926 SingleColourLayer::toXml(stream, indent,
Chris@316 927 extraAttributes +
Chris@316 928 QString(" plotStyle=\"%1\"")
Chris@316 929 .arg(m_plotStyle));
Chris@6 930 }
Chris@0 931
Chris@11 932 void
Chris@11 933 TimeInstantLayer::setProperties(const QXmlAttributes &attributes)
Chris@11 934 {
Chris@287 935 SingleColourLayer::setProperties(attributes);
Chris@28 936
Chris@28 937 bool ok;
Chris@28 938 PlotStyle style = (PlotStyle)
Chris@28 939 attributes.value("plotStyle").toInt(&ok);
Chris@28 940 if (ok) setPlotStyle(style);
Chris@11 941 }
Chris@11 942