annotate layer/ImageLayer.cpp @ 1511:a473b73fb045 time-frequency-boxes

Introduce time-frequency box layer
author Chris Cannam
date Thu, 19 Sep 2019 15:18:28 +0100
parents e540aa5d89cd
children 37df1530519d
rev   line source
Chris@303 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@303 2
Chris@303 3 /*
Chris@303 4 Sonic Visualiser
Chris@303 5 An audio file viewer and annotation editor.
Chris@303 6 Centre for Digital Music, Queen Mary, University of London.
Chris@303 7 This file copyright 2006 Chris Cannam.
Chris@303 8
Chris@303 9 This program is free software; you can redistribute it and/or
Chris@303 10 modify it under the terms of the GNU General Public License as
Chris@303 11 published by the Free Software Foundation; either version 2 of the
Chris@303 12 License, or (at your option) any later version. See the file
Chris@303 13 COPYING included with this distribution for more information.
Chris@303 14 */
Chris@303 15
Chris@303 16 #include "ImageLayer.h"
Chris@303 17
Chris@303 18 #include "data/model/Model.h"
Chris@303 19 #include "base/RealTime.h"
Chris@303 20 #include "base/Profiler.h"
Chris@303 21 #include "view/View.h"
Chris@303 22
Chris@303 23 #include "data/model/ImageModel.h"
Chris@318 24 #include "data/fileio/FileSource.h"
Chris@303 25
Chris@303 26 #include "widgets/ImageDialog.h"
Chris@378 27 #include "widgets/ProgressDialog.h"
Chris@303 28
Chris@303 29 #include <QPainter>
Chris@303 30 #include <QMouseEvent>
Chris@303 31 #include <QInputDialog>
Chris@305 32 #include <QMutexLocker>
Chris@316 33 #include <QTextStream>
Chris@360 34 #include <QMessageBox>
Chris@303 35
Chris@303 36 #include <iostream>
Chris@303 37 #include <cmath>
Chris@303 38
Chris@303 39 ImageLayer::ImageMap
Chris@303 40 ImageLayer::m_images;
Chris@303 41
Chris@305 42 QMutex
Chris@305 43 ImageLayer::m_imageMapMutex;
Chris@305 44
Chris@303 45 ImageLayer::ImageLayer() :
Chris@303 46 m_editing(false),
Chris@1408 47 m_editingCommand(nullptr)
Chris@303 48 {
Chris@305 49 }
Chris@305 50
Chris@305 51 ImageLayer::~ImageLayer()
Chris@305 52 {
Chris@464 53 for (FileSourceMap::iterator i = m_fileSources.begin();
Chris@464 54 i != m_fileSources.end(); ++i) {
Chris@305 55 delete i->second;
Chris@305 56 }
Chris@303 57 }
Chris@303 58
Chris@1469 59 int
Chris@1469 60 ImageLayer::getCompletion(LayerGeometryProvider *) const
Chris@1469 61 {
Chris@1469 62 auto model = ModelById::get(m_model);
Chris@1469 63 if (model) return model->getCompletion();
Chris@1469 64 else return 0;
Chris@1469 65 }
Chris@1469 66
Chris@303 67 void
Chris@1469 68 ImageLayer::setModel(ModelId modelId)
Chris@303 69 {
Chris@1471 70 auto newModel = ModelById::getAs<ImageModel>(modelId);
Chris@1471 71
Chris@1471 72 if (!modelId.isNone() && !newModel) {
Chris@1471 73 throw std::logic_error("Not an ImageModel");
Chris@1471 74 }
Chris@1471 75
Chris@1469 76 if (m_model == modelId) return;
Chris@1469 77 m_model = modelId;
Chris@303 78
Chris@1471 79 if (newModel) {
Chris@1471 80 connectSignals(m_model);
Chris@1471 81 }
Chris@305 82
Chris@303 83 emit modelReplaced();
Chris@303 84 }
Chris@303 85
Chris@303 86 Layer::PropertyList
Chris@303 87 ImageLayer::getProperties() const
Chris@303 88 {
Chris@303 89 return Layer::getProperties();
Chris@303 90 }
Chris@303 91
Chris@303 92 QString
Chris@805 93 ImageLayer::getPropertyLabel(const PropertyName &) const
Chris@303 94 {
Chris@303 95 return "";
Chris@303 96 }
Chris@303 97
Chris@303 98 Layer::PropertyType
Chris@303 99 ImageLayer::getPropertyType(const PropertyName &name) const
Chris@303 100 {
Chris@303 101 return Layer::getPropertyType(name);
Chris@303 102 }
Chris@303 103
Chris@303 104 int
Chris@303 105 ImageLayer::getPropertyRangeAndValue(const PropertyName &name,
Chris@1266 106 int *min, int *max, int *deflt) const
Chris@303 107 {
Chris@303 108 return Layer::getPropertyRangeAndValue(name, min, max, deflt);
Chris@303 109 }
Chris@303 110
Chris@303 111 QString
Chris@303 112 ImageLayer::getPropertyValueLabel(const PropertyName &name,
Chris@1266 113 int value) const
Chris@303 114 {
Chris@303 115 return Layer::getPropertyValueLabel(name, value);
Chris@303 116 }
Chris@303 117
Chris@303 118 void
Chris@303 119 ImageLayer::setProperty(const PropertyName &name, int value)
Chris@303 120 {
Chris@303 121 Layer::setProperty(name, value);
Chris@303 122 }
Chris@303 123
Chris@303 124 bool
Chris@905 125 ImageLayer::getValueExtents(double &, double &, bool &, QString &) const
Chris@303 126 {
Chris@303 127 return false;
Chris@303 128 }
Chris@303 129
Chris@303 130 bool
Chris@918 131 ImageLayer::isLayerScrollable(const LayerGeometryProvider *) const
Chris@303 132 {
Chris@304 133 return true;
Chris@303 134 }
Chris@303 135
Chris@1438 136 EventVector
Chris@918 137 ImageLayer::getLocalPoints(LayerGeometryProvider *v, int x, int ) const
Chris@303 138 {
Chris@1469 139 auto model = ModelById::getAs<ImageModel>(m_model);
Chris@1469 140 if (!model) return {};
Chris@303 141
Chris@587 142 // SVDEBUG << "ImageLayer::getLocalPoints(" << x << "," << y << "):";
Chris@1469 143 EventVector points(model->getAllEvents());
Chris@303 144
Chris@1438 145 EventVector rv;
Chris@303 146
Chris@1438 147 for (EventVector::const_iterator i = points.begin(); i != points.end(); ) {
Chris@303 148
Chris@1438 149 Event p(*i);
Chris@1438 150 int px = v->getXForFrame(p.getFrame());
Chris@304 151 if (px > x) break;
Chris@303 152
Chris@304 153 ++i;
Chris@304 154 if (i != points.end()) {
Chris@1438 155 int nx = v->getXForFrame(i->getFrame());
Chris@304 156 if (nx < x) {
Chris@304 157 // as we aim not to overlap the images, if the following
Chris@304 158 // image begins to the left of a point then the current
Chris@304 159 // one may be assumed to end to the left of it as well.
Chris@304 160 continue;
Chris@304 161 }
Chris@304 162 }
Chris@303 163
Chris@304 164 // this image is a candidate, test it properly
Chris@304 165
Chris@304 166 int width = 32;
Chris@1438 167 if (m_scaled[v].find(p.getURI()) != m_scaled[v].end()) {
Chris@1438 168 width = m_scaled[v][p.getURI()].width();
Chris@587 169 // SVDEBUG << "scaled width = " << width << endl;
Chris@304 170 }
Chris@304 171
Chris@304 172 if (x >= px && x < px + width) {
Chris@1438 173 rv.push_back(p);
Chris@303 174 }
Chris@303 175 }
Chris@303 176
Chris@682 177 // cerr << rv.size() << " point(s)" << endl;
Chris@303 178
Chris@303 179 return rv;
Chris@303 180 }
Chris@303 181
Chris@303 182 QString
Chris@918 183 ImageLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &pos) const
Chris@303 184 {
Chris@303 185 int x = pos.x();
Chris@303 186
Chris@1469 187 auto model = ModelById::getAs<ImageModel>(m_model);
Chris@1469 188 if (!model || !model->getSampleRate()) return "";
Chris@303 189
Chris@1438 190 EventVector points = getLocalPoints(v, x, pos.y());
Chris@303 191
Chris@303 192 if (points.empty()) {
Chris@1469 193 if (!model->isReady()) {
Chris@1266 194 return tr("In progress");
Chris@1266 195 } else {
Chris@1266 196 return "";
Chris@1266 197 }
Chris@303 198 }
Chris@303 199
Chris@806 200 // int useFrame = points.begin()->frame;
Chris@303 201
Chris@1469 202 // RealTime rt = RealTime::frame2RealTime(useFrame, model->getSampleRate());
Chris@303 203
Chris@303 204 QString text;
Chris@303 205 /*
Chris@303 206 if (points.begin()->label == "") {
Chris@1266 207 text = QString(tr("Time:\t%1\nHeight:\t%2\nLabel:\t%3"))
Chris@1266 208 .arg(rt.toText(true).c_str())
Chris@1266 209 .arg(points.begin()->height)
Chris@1266 210 .arg(points.begin()->label);
Chris@303 211 }
Chris@303 212
Chris@303 213 pos = QPoint(v->getXForFrame(useFrame),
Chris@1266 214 getYForHeight(v, points.begin()->height));
Chris@303 215 */
Chris@303 216 return text;
Chris@303 217 }
Chris@303 218
Chris@303 219
Chris@303 220 //!!! too much overlap with TimeValueLayer/TimeInstantLayer/TextLayer
Chris@303 221
Chris@303 222 bool
Chris@918 223 ImageLayer::snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
Chris@1438 224 int &resolution,
Chris@1438 225 SnapType snap) const
Chris@303 226 {
Chris@1469 227 auto model = ModelById::getAs<ImageModel>(m_model);
Chris@1469 228 if (!model) {
Chris@1266 229 return Layer::snapToFeatureFrame(v, frame, resolution, snap);
Chris@303 230 }
Chris@303 231
Chris@1469 232 resolution = model->getResolution();
Chris@303 233
Chris@303 234 if (snap == SnapNeighbouring) {
Chris@1438 235 EventVector points = getLocalPoints(v, v->getXForFrame(frame), -1);
Chris@1266 236 if (points.empty()) return false;
Chris@1438 237 frame = points.begin()->getFrame();
Chris@1266 238 return true;
Chris@303 239 }
Chris@303 240
Chris@1438 241 Event e;
Chris@1469 242 if (model->getNearestEventMatching
Chris@1438 243 (frame,
Chris@1438 244 [](Event) { return true; },
Chris@1438 245 snap == SnapLeft ? EventSeries::Backward : EventSeries::Forward,
Chris@1438 246 e)) {
Chris@1438 247 frame = e.getFrame();
Chris@1438 248 return true;
Chris@303 249 }
Chris@303 250
Chris@1438 251 return false;
Chris@303 252 }
Chris@303 253
Chris@303 254 void
Chris@916 255 ImageLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
Chris@303 256 {
Chris@1469 257 auto model = ModelById::getAs<ImageModel>(m_model);
Chris@1469 258 if (!model || !model->isOK()) return;
Chris@303 259
Chris@1469 260 sv_samplerate_t sampleRate = model->getSampleRate();
Chris@303 261 if (!sampleRate) return;
Chris@303 262
Chris@303 263 // Profiler profiler("ImageLayer::paint", true);
Chris@303 264
Chris@304 265 // int x0 = rect.left(), x1 = rect.right();
Chris@918 266 int x0 = 0, x1 = v->getPaintWidth();
Chris@304 267
Chris@905 268 sv_frame_t frame0 = v->getFrameForX(x0);
Chris@905 269 sv_frame_t frame1 = v->getFrameForX(x1);
Chris@303 270
Chris@1469 271 EventVector points(model->getEventsWithin(frame0, frame1 - frame0, 2));
Chris@303 272 if (points.empty()) return;
Chris@303 273
Chris@304 274 paint.save();
Chris@918 275 paint.setClipRect(rect.x(), 0, rect.width(), v->getPaintHeight());
Chris@304 276
Chris@303 277 QColor penColour;
Chris@303 278 penColour = v->getForeground();
Chris@303 279
Chris@304 280 QColor brushColour;
Chris@304 281 brushColour = v->getBackground();
Chris@303 282
Chris@304 283 int h, s, val;
Chris@304 284 brushColour.getHsv(&h, &s, &val);
Chris@304 285 brushColour.setHsv(h, s, 255, 240);
Chris@303 286
Chris@304 287 paint.setPen(penColour);
Chris@304 288 paint.setBrush(brushColour);
Chris@304 289 paint.setRenderHint(QPainter::Antialiasing, true);
Chris@303 290
Chris@1438 291 for (EventVector::const_iterator i = points.begin();
Chris@1266 292 i != points.end(); ++i) {
Chris@303 293
Chris@1438 294 Event p(*i);
Chris@303 295
Chris@1438 296 int x = v->getXForFrame(p.getFrame());
Chris@303 297
Chris@303 298 int nx = x + 2000;
Chris@1438 299 EventVector::const_iterator j = i;
Chris@303 300 ++j;
Chris@303 301 if (j != points.end()) {
Chris@1438 302 int jx = v->getXForFrame(j->getFrame());
Chris@303 303 if (jx < nx) nx = jx;
Chris@303 304 }
Chris@303 305
Chris@304 306 drawImage(v, paint, p, x, nx);
Chris@304 307 }
Chris@303 308
Chris@304 309 paint.setRenderHint(QPainter::Antialiasing, false);
Chris@304 310 paint.restore();
Chris@304 311 }
Chris@303 312
Chris@304 313 void
Chris@1438 314 ImageLayer::drawImage(LayerGeometryProvider *v, QPainter &paint, const Event &p,
Chris@304 315 int x, int nx) const
Chris@304 316 {
Chris@1438 317 QString label = p.getLabel();
Chris@1438 318 QString imageName = p.getURI();
Chris@304 319
Chris@304 320 QImage image;
Chris@304 321 QString additionalText;
Chris@304 322
Chris@304 323 QSize imageSize;
Chris@304 324 if (!getImageOriginalSize(imageName, imageSize)) {
Chris@304 325 image = QImage(":icons/emptypage.png");
Chris@304 326 imageSize = image.size();
Chris@304 327 additionalText = imageName;
Chris@304 328 }
Chris@304 329
Chris@304 330 int topMargin = 10;
Chris@304 331 int bottomMargin = 10;
Chris@304 332 int spacing = 5;
Chris@304 333
Chris@918 334 if (v->getPaintHeight() < 100) {
Chris@304 335 topMargin = 5;
Chris@304 336 bottomMargin = 5;
Chris@304 337 }
Chris@304 338
Chris@918 339 int maxBoxHeight = v->getPaintHeight() - topMargin - bottomMargin;
Chris@304 340
Chris@304 341 int availableWidth = nx - x - 3;
Chris@304 342 if (availableWidth < 20) availableWidth = 20;
Chris@304 343
Chris@304 344 QRect labelRect;
Chris@304 345
Chris@304 346 if (label != "") {
Chris@304 347
Chris@918 348 int likelyHeight = v->getPaintHeight() / 4;
Chris@304 349
Chris@304 350 int likelyWidth = // available height times image aspect
Chris@304 351 ((maxBoxHeight - likelyHeight) * imageSize.width())
Chris@304 352 / imageSize.height();
Chris@304 353
Chris@304 354 if (likelyWidth > imageSize.width()) {
Chris@304 355 likelyWidth = imageSize.width();
Chris@303 356 }
Chris@303 357
Chris@304 358 if (likelyWidth > availableWidth) {
Chris@304 359 likelyWidth = availableWidth;
Chris@303 360 }
Chris@303 361
Chris@1471 362 // Qt 5.13 deprecates QFontMetrics::width(), but its suggested
Chris@1471 363 // replacement (horizontalAdvance) was only added in Qt 5.11
Chris@1471 364 // which is too new for us
Chris@1471 365 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
Chris@1471 366
Chris@304 367 int singleWidth = paint.fontMetrics().width(label);
Chris@304 368 if (singleWidth < availableWidth && singleWidth < likelyWidth * 2) {
Chris@304 369 likelyWidth = singleWidth + 4;
Chris@303 370 }
Chris@303 371
Chris@304 372 labelRect = paint.fontMetrics().boundingRect
Chris@304 373 (QRect(0, 0, likelyWidth, likelyHeight),
Chris@304 374 Qt::AlignCenter | Qt::TextWordWrap, label);
Chris@303 375
Chris@304 376 labelRect.setWidth(labelRect.width() + 6);
Chris@303 377 }
Chris@303 378
Chris@304 379 if (image.isNull()) {
Chris@304 380 image = getImage(v, imageName,
Chris@304 381 QSize(availableWidth,
Chris@304 382 maxBoxHeight - labelRect.height()));
Chris@304 383 }
Chris@304 384
Chris@304 385 int boxWidth = image.width();
Chris@304 386 if (boxWidth < labelRect.width()) {
Chris@304 387 boxWidth = labelRect.width();
Chris@304 388 }
Chris@304 389
Chris@304 390 int boxHeight = image.height();
Chris@304 391 if (label != "") {
Chris@304 392 boxHeight += labelRect.height() + spacing;
Chris@304 393 }
Chris@304 394
Chris@304 395 int division = image.height();
Chris@304 396
Chris@304 397 if (additionalText != "") {
Chris@304 398
Chris@304 399 paint.save();
Chris@304 400
Chris@304 401 QFont font(paint.font());
Chris@304 402 font.setItalic(true);
Chris@304 403 paint.setFont(font);
Chris@304 404
Chris@304 405 int tw = paint.fontMetrics().width(additionalText);
Chris@304 406 if (tw > availableWidth) {
Chris@304 407 tw = availableWidth;
Chris@304 408 }
Chris@304 409 if (boxWidth < tw) {
Chris@304 410 boxWidth = tw;
Chris@304 411 }
Chris@304 412 boxHeight += paint.fontMetrics().height();
Chris@304 413 division += paint.fontMetrics().height();
Chris@304 414 }
Chris@304 415
Chris@918 416 bottomMargin = v->getPaintHeight() - topMargin - boxHeight;
Chris@918 417 if (bottomMargin > topMargin + v->getPaintHeight()/7) {
Chris@918 418 topMargin += v->getPaintHeight()/8;
Chris@918 419 bottomMargin -= v->getPaintHeight()/8;
Chris@304 420 }
Chris@304 421
Chris@304 422 paint.drawRect(x - 1,
Chris@304 423 topMargin - 1,
Chris@304 424 boxWidth + 2,
Chris@304 425 boxHeight + 2);
Chris@304 426
Chris@304 427 int imageY;
Chris@304 428 if (label != "") {
Chris@304 429 imageY = topMargin + labelRect.height() + spacing;
Chris@304 430 } else {
Chris@304 431 imageY = topMargin;
Chris@304 432 }
Chris@304 433
Chris@304 434 paint.drawImage(x + (boxWidth - image.width())/2,
Chris@304 435 imageY,
Chris@304 436 image);
Chris@304 437
Chris@304 438 if (additionalText != "") {
Chris@304 439 paint.drawText(x,
Chris@304 440 imageY + image.height() + paint.fontMetrics().ascent(),
Chris@304 441 additionalText);
Chris@304 442 paint.restore();
Chris@304 443 }
Chris@304 444
Chris@304 445 if (label != "") {
Chris@304 446 paint.drawLine(x,
Chris@304 447 topMargin + labelRect.height() + spacing,
Chris@304 448 x + boxWidth,
Chris@304 449 topMargin + labelRect.height() + spacing);
Chris@304 450
Chris@304 451 paint.drawText(QRect(x,
Chris@304 452 topMargin,
Chris@304 453 boxWidth,
Chris@304 454 labelRect.height()),
Chris@304 455 Qt::AlignCenter | Qt::TextWordWrap,
Chris@304 456 label);
Chris@304 457 }
Chris@303 458 }
Chris@303 459
Chris@303 460 void
Chris@918 461 ImageLayer::setLayerDormant(const LayerGeometryProvider *v, bool dormant)
Chris@303 462 {
Chris@303 463 if (dormant) {
Chris@303 464 // Delete the images named in the view's scaled map from the
Chris@303 465 // general image map as well. They can always be re-loaded
Chris@303 466 // if it turns out another view still needs them.
Chris@305 467 QMutexLocker locker(&m_imageMapMutex);
Chris@303 468 for (ImageMap::iterator i = m_scaled[v].begin();
Chris@303 469 i != m_scaled[v].end(); ++i) {
Chris@303 470 m_images.erase(i->first);
Chris@303 471 }
Chris@303 472 m_scaled.erase(v);
Chris@303 473 }
Chris@303 474 }
Chris@303 475
Chris@303 476 //!!! how to reap no-longer-used images?
Chris@303 477
Chris@304 478 bool
Chris@304 479 ImageLayer::getImageOriginalSize(QString name, QSize &size) const
Chris@303 480 {
Chris@682 481 // cerr << "getImageOriginalSize: \"" << name << "\"" << endl;
Chris@305 482
Chris@305 483 QMutexLocker locker(&m_imageMapMutex);
Chris@303 484 if (m_images.find(name) == m_images.end()) {
Chris@682 485 // cerr << "don't have, trying to open local" << endl;
Chris@305 486 m_images[name] = QImage(getLocalFilename(name));
Chris@303 487 }
Chris@304 488 if (m_images[name].isNull()) {
Chris@682 489 // cerr << "null image" << endl;
Chris@304 490 return false;
Chris@304 491 } else {
Chris@304 492 size = m_images[name].size();
Chris@304 493 return true;
Chris@304 494 }
Chris@303 495 }
Chris@303 496
Chris@303 497 QImage
Chris@918 498 ImageLayer::getImage(LayerGeometryProvider *v, QString name, QSize maxSize) const
Chris@303 499 {
Chris@587 500 // SVDEBUG << "ImageLayer::getImage(" << v << ", " << name << ", ("
Chris@585 501 // << maxSize.width() << "x" << maxSize.height() << "))" << endl;
Chris@303 502
Chris@304 503 if (!m_scaled[v][name].isNull() &&
Chris@304 504 ((m_scaled[v][name].width() == maxSize.width() &&
Chris@304 505 m_scaled[v][name].height() <= maxSize.height()) ||
Chris@304 506 (m_scaled[v][name].width() <= maxSize.width() &&
Chris@304 507 m_scaled[v][name].height() == maxSize.height()))) {
Chris@682 508 // cerr << "cache hit" << endl;
Chris@303 509 return m_scaled[v][name];
Chris@303 510 }
Chris@303 511
Chris@305 512 QMutexLocker locker(&m_imageMapMutex);
Chris@305 513
Chris@303 514 if (m_images.find(name) == m_images.end()) {
Chris@305 515 m_images[name] = QImage(getLocalFilename(name));
Chris@303 516 }
Chris@303 517
Chris@303 518 if (m_images[name].isNull()) {
Chris@682 519 // cerr << "null image" << endl;
Chris@303 520 m_scaled[v][name] = QImage();
Chris@304 521 } else if (m_images[name].width() <= maxSize.width() &&
Chris@304 522 m_images[name].height() <= maxSize.height()) {
Chris@304 523 m_scaled[v][name] = m_images[name];
Chris@303 524 } else {
Chris@303 525 m_scaled[v][name] =
Chris@303 526 m_images[name].scaled(maxSize,
Chris@303 527 Qt::KeepAspectRatio,
Chris@303 528 Qt::SmoothTransformation);
Chris@303 529 }
Chris@303 530
Chris@303 531 return m_scaled[v][name];
Chris@303 532 }
Chris@303 533
Chris@303 534 void
Chris@918 535 ImageLayer::drawStart(LayerGeometryProvider *v, QMouseEvent *e)
Chris@303 536 {
Chris@587 537 // SVDEBUG << "ImageLayer::drawStart(" << e->x() << "," << e->y() << ")" << endl;
Chris@303 538
Chris@1469 539 auto model = ModelById::getAs<ImageModel>(m_model);
Chris@1469 540 if (!model) {
Chris@1266 541 SVDEBUG << "ImageLayer::drawStart: no model" << endl;
Chris@1266 542 return;
Chris@303 543 }
Chris@303 544
Chris@905 545 sv_frame_t frame = v->getFrameForX(e->x());
Chris@303 546 if (frame < 0) frame = 0;
Chris@1469 547 frame = frame / model->getResolution() * model->getResolution();
Chris@303 548
Chris@1438 549 m_editingPoint = Event(frame);
Chris@303 550 m_originalPoint = m_editingPoint;
Chris@303 551
Chris@376 552 if (m_editingCommand) finish(m_editingCommand);
Chris@1470 553 m_editingCommand = new ChangeEventsCommand(m_model.untyped, "Add Image");
Chris@1438 554 m_editingCommand->add(m_editingPoint);
Chris@303 555
Chris@303 556 m_editing = true;
Chris@303 557 }
Chris@303 558
Chris@303 559 void
Chris@918 560 ImageLayer::drawDrag(LayerGeometryProvider *v, QMouseEvent *e)
Chris@303 561 {
Chris@587 562 // SVDEBUG << "ImageLayer::drawDrag(" << e->x() << "," << e->y() << ")" << endl;
Chris@303 563
Chris@1469 564 auto model = ModelById::getAs<ImageModel>(m_model);
Chris@1469 565 if (!model || !m_editing) return;
Chris@303 566
Chris@905 567 sv_frame_t frame = v->getFrameForX(e->x());
Chris@303 568 if (frame < 0) frame = 0;
Chris@1469 569 frame = frame / model->getResolution() * model->getResolution();
Chris@303 570
Chris@1438 571 m_editingCommand->remove(m_editingPoint);
Chris@1438 572 m_editingPoint = m_editingPoint
Chris@1438 573 .withFrame(frame);
Chris@1438 574 m_editingCommand->add(m_editingPoint);
Chris@303 575 }
Chris@303 576
Chris@303 577 void
Chris@918 578 ImageLayer::drawEnd(LayerGeometryProvider *, QMouseEvent *)
Chris@303 579 {
Chris@587 580 // SVDEBUG << "ImageLayer::drawEnd(" << e->x() << "," << e->y() << ")" << endl;
Chris@1469 581 auto model = ModelById::getAs<ImageModel>(m_model);
Chris@1469 582 if (!model || !m_editing) return;
Chris@303 583
Chris@307 584 ImageDialog dialog(tr("Select image"), "", "");
Chris@305 585
Chris@1438 586 m_editingCommand->remove(m_editingPoint);
Chris@1438 587
Chris@303 588 if (dialog.exec() == QDialog::Accepted) {
Chris@305 589
Chris@464 590 checkAddSource(dialog.getImage());
Chris@305 591
Chris@1438 592 m_editingPoint = m_editingPoint
Chris@1438 593 .withURI(dialog.getImage())
Chris@1438 594 .withLabel(dialog.getLabel());
Chris@1438 595 m_editingCommand->add(m_editingPoint);
Chris@303 596 }
Chris@303 597
Chris@376 598 finish(m_editingCommand);
Chris@1408 599 m_editingCommand = nullptr;
Chris@303 600 m_editing = false;
Chris@303 601 }
Chris@303 602
Chris@312 603 bool
Chris@905 604 ImageLayer::addImage(sv_frame_t frame, QString url)
Chris@312 605 {
Chris@312 606 QImage image(getLocalFilename(url));
Chris@312 607 if (image.isNull()) {
Chris@682 608 cerr << "Failed to open image from url \"" << url << "\" (local filename \"" << getLocalFilename(url) << "\"" << endl;
Chris@464 609 delete m_fileSources[url];
Chris@464 610 m_fileSources.erase(url);
Chris@312 611 return false;
Chris@312 612 }
Chris@312 613
Chris@1438 614 Event point = Event(frame).withURI(url);
Chris@1469 615 auto command =
Chris@1470 616 new ChangeEventsCommand(m_model.untyped, "Add Image");
Chris@1438 617 command->add(point);
Chris@376 618 finish(command);
Chris@312 619 return true;
Chris@312 620 }
Chris@312 621
Chris@303 622 void
Chris@918 623 ImageLayer::editStart(LayerGeometryProvider *v, QMouseEvent *e)
Chris@303 624 {
Chris@587 625 // SVDEBUG << "ImageLayer::editStart(" << e->x() << "," << e->y() << ")" << endl;
Chris@303 626
Chris@1469 627 auto model = ModelById::getAs<ImageModel>(m_model);
Chris@1469 628 if (!model) return;
Chris@303 629
Chris@1438 630 EventVector points = getLocalPoints(v, e->x(), e->y());
Chris@303 631 if (points.empty()) return;
Chris@303 632
Chris@303 633 m_editOrigin = e->pos();
Chris@303 634 m_editingPoint = *points.begin();
Chris@303 635 m_originalPoint = m_editingPoint;
Chris@303 636
Chris@303 637 if (m_editingCommand) {
Chris@1266 638 finish(m_editingCommand);
Chris@1408 639 m_editingCommand = nullptr;
Chris@303 640 }
Chris@303 641
Chris@303 642 m_editing = true;
Chris@303 643 }
Chris@303 644
Chris@303 645 void
Chris@918 646 ImageLayer::editDrag(LayerGeometryProvider *v, QMouseEvent *e)
Chris@303 647 {
Chris@1469 648 auto model = ModelById::getAs<ImageModel>(m_model);
Chris@1469 649 if (!model || !m_editing) return;
Chris@303 650
Chris@905 651 sv_frame_t frameDiff = v->getFrameForX(e->x()) - v->getFrameForX(m_editOrigin.x());
Chris@1438 652 sv_frame_t frame = m_originalPoint.getFrame() + frameDiff;
Chris@303 653
Chris@303 654 if (frame < 0) frame = 0;
Chris@1469 655 frame = (frame / model->getResolution()) * model->getResolution();
Chris@303 656
Chris@303 657 if (!m_editingCommand) {
Chris@1470 658 m_editingCommand = new ChangeEventsCommand(m_model.untyped, tr("Move Image"));
Chris@303 659 }
Chris@303 660
Chris@1438 661 m_editingCommand->remove(m_editingPoint);
Chris@1438 662 m_editingPoint = m_editingPoint
Chris@1438 663 .withFrame(frame);
Chris@1438 664 m_editingCommand->add(m_editingPoint);
Chris@303 665 }
Chris@303 666
Chris@303 667 void
Chris@918 668 ImageLayer::editEnd(LayerGeometryProvider *, QMouseEvent *)
Chris@303 669 {
Chris@587 670 // SVDEBUG << "ImageLayer::editEnd(" << e->x() << "," << e->y() << ")" << endl;
Chris@1469 671 auto model = ModelById::getAs<ImageModel>(m_model);
Chris@1469 672 if (!model || !m_editing) return;
Chris@303 673
Chris@303 674 if (m_editingCommand) {
Chris@1266 675 finish(m_editingCommand);
Chris@303 676 }
Chris@303 677
Chris@1408 678 m_editingCommand = nullptr;
Chris@303 679 m_editing = false;
Chris@303 680 }
Chris@303 681
Chris@303 682 bool
Chris@918 683 ImageLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e)
Chris@303 684 {
Chris@1469 685 auto model = ModelById::getAs<ImageModel>(m_model);
Chris@1469 686 if (!model) return false;
Chris@303 687
Chris@1438 688 EventVector points = getLocalPoints(v, e->x(), e->y());
Chris@303 689 if (points.empty()) return false;
Chris@303 690
Chris@1438 691 QString image = points.begin()->getURI();
Chris@1438 692 QString label = points.begin()->getLabel();
Chris@303 693
Chris@303 694 ImageDialog dialog(tr("Select image"),
Chris@303 695 image,
Chris@303 696 label);
Chris@303 697
Chris@303 698 if (dialog.exec() == QDialog::Accepted) {
Chris@305 699
Chris@464 700 checkAddSource(dialog.getImage());
Chris@305 701
Chris@1469 702 auto command =
Chris@1470 703 new ChangeEventsCommand(m_model.untyped, tr("Edit Image"));
Chris@1438 704 command->remove(*points.begin());
Chris@1438 705 command->add(points.begin()->
Chris@1438 706 withURI(dialog.getImage()).withLabel(dialog.getLabel()));
Chris@1438 707 finish(command);
Chris@303 708 }
Chris@303 709
Chris@303 710 return true;
Chris@303 711 }
Chris@303 712
Chris@303 713 void
Chris@905 714 ImageLayer::moveSelection(Selection s, sv_frame_t newStartFrame)
Chris@303 715 {
Chris@1469 716 auto model = ModelById::getAs<ImageModel>(m_model);
Chris@1469 717 if (!model) return;
Chris@303 718
Chris@1469 719 auto command =
Chris@1470 720 new ChangeEventsCommand(m_model.untyped, tr("Drag Selection"));
Chris@303 721
Chris@1438 722 EventVector points =
Chris@1469 723 model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
Chris@303 724
Chris@1438 725 for (Event p: points) {
Chris@1438 726 command->remove(p);
Chris@1438 727 Event moved = p.withFrame(p.getFrame() +
Chris@1438 728 newStartFrame - s.getStartFrame());
Chris@1438 729 command->add(moved);
Chris@303 730 }
Chris@303 731
Chris@376 732 finish(command);
Chris@303 733 }
Chris@303 734
Chris@303 735 void
Chris@303 736 ImageLayer::resizeSelection(Selection s, Selection newSize)
Chris@303 737 {
Chris@1469 738 auto model = ModelById::getAs<ImageModel>(m_model);
Chris@1469 739 if (!model) return;
Chris@303 740
Chris@1469 741 auto command =
Chris@1470 742 new ChangeEventsCommand(m_model.untyped, tr("Resize Selection"));
Chris@303 743
Chris@1438 744 EventVector points =
Chris@1469 745 model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
Chris@303 746
Chris@1438 747 double ratio = double(newSize.getDuration()) / double(s.getDuration());
Chris@1438 748 double oldStart = double(s.getStartFrame());
Chris@1438 749 double newStart = double(newSize.getStartFrame());
Chris@1438 750
Chris@1438 751 for (Event p: points) {
Chris@303 752
Chris@1438 753 double newFrame = (double(p.getFrame()) - oldStart) * ratio + newStart;
Chris@303 754
Chris@1438 755 Event newPoint = p
Chris@1438 756 .withFrame(lrint(newFrame));
Chris@1438 757 command->remove(p);
Chris@1438 758 command->add(newPoint);
Chris@303 759 }
Chris@303 760
Chris@376 761 finish(command);
Chris@303 762 }
Chris@303 763
Chris@303 764 void
Chris@303 765 ImageLayer::deleteSelection(Selection s)
Chris@303 766 {
Chris@1469 767 auto model = ModelById::getAs<ImageModel>(m_model);
Chris@1469 768 if (!model) return;
Chris@303 769
Chris@1469 770 auto command =
Chris@1470 771 new ChangeEventsCommand(m_model.untyped, tr("Delete Selection"));
Chris@303 772
Chris@1438 773 EventVector points =
Chris@1469 774 model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
Chris@303 775
Chris@1438 776 for (Event p: points) {
Chris@1438 777 command->remove(p);
Chris@303 778 }
Chris@303 779
Chris@376 780 finish(command);
Chris@303 781 }
Chris@303 782
Chris@303 783 void
Chris@918 784 ImageLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to)
Chris@303 785 {
Chris@1469 786 auto model = ModelById::getAs<ImageModel>(m_model);
Chris@1469 787 if (!model) return;
Chris@303 788
Chris@1438 789 EventVector points =
Chris@1469 790 model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
Chris@303 791
Chris@1438 792 for (Event p: points) {
Chris@1438 793 to.addPoint(p.withReferenceFrame(alignToReference(v, p.getFrame())));
Chris@303 794 }
Chris@303 795 }
Chris@303 796
Chris@303 797 bool
Chris@918 798 ImageLayer::paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t /* frameOffset */, bool /* interactive */)
Chris@303 799 {
Chris@1469 800 auto model = ModelById::getAs<ImageModel>(m_model);
Chris@1469 801 if (!model) return false;
Chris@303 802
Chris@1423 803 const EventVector &points = from.getPoints();
Chris@303 804
Chris@360 805 bool realign = false;
Chris@360 806
Chris@360 807 if (clipboardHasDifferentAlignment(v, from)) {
Chris@360 808
Chris@360 809 QMessageBox::StandardButton button =
Chris@918 810 QMessageBox::question(v->getView(), tr("Re-align pasted items?"),
Chris@360 811 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 812 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
Chris@360 813 QMessageBox::Yes);
Chris@360 814
Chris@360 815 if (button == QMessageBox::Cancel) {
Chris@360 816 return false;
Chris@360 817 }
Chris@360 818
Chris@360 819 if (button == QMessageBox::Yes) {
Chris@360 820 realign = true;
Chris@360 821 }
Chris@360 822 }
Chris@360 823
Chris@1470 824 auto command = new ChangeEventsCommand(m_model.untyped, tr("Paste"));
Chris@303 825
Chris@1423 826 for (EventVector::const_iterator i = points.begin();
Chris@303 827 i != points.end(); ++i) {
Chris@303 828
Chris@905 829 sv_frame_t frame = 0;
Chris@360 830
Chris@360 831 if (!realign) {
Chris@360 832
Chris@360 833 frame = i->getFrame();
Chris@360 834
Chris@360 835 } else {
Chris@360 836
Chris@1423 837 if (i->hasReferenceFrame()) {
Chris@360 838 frame = i->getReferenceFrame();
Chris@360 839 frame = alignFromReference(v, frame);
Chris@360 840 } else {
Chris@360 841 frame = i->getFrame();
Chris@360 842 }
Chris@303 843 }
Chris@360 844
Chris@1438 845 Event p = *i;
Chris@1438 846 Event newPoint = p;
Chris@303 847
Chris@303 848 //!!! inadequate
Chris@303 849
Chris@1438 850 if (!p.hasLabel()) {
Chris@1438 851 if (p.hasValue()) {
Chris@1438 852 newPoint = newPoint.withLabel(QString("%1").arg(p.getValue()));
Chris@1438 853 } else {
Chris@1438 854 newPoint = newPoint.withLabel(tr("New Point"));
Chris@1438 855 }
Chris@303 856 }
Chris@303 857
Chris@1438 858 command->add(newPoint);
Chris@303 859 }
Chris@303 860
Chris@376 861 finish(command);
Chris@303 862 return true;
Chris@303 863 }
Chris@303 864
Chris@303 865 QString
Chris@305 866 ImageLayer::getLocalFilename(QString img) const
Chris@305 867 {
Chris@464 868 if (m_fileSources.find(img) == m_fileSources.end()) {
Chris@464 869 checkAddSource(img);
Chris@464 870 if (m_fileSources.find(img) == m_fileSources.end()) {
Chris@312 871 return img;
Chris@312 872 }
Chris@305 873 }
Chris@464 874 return m_fileSources[img]->getLocalFilename();
Chris@305 875 }
Chris@305 876
Chris@305 877 void
Chris@464 878 ImageLayer::checkAddSource(QString img) const
Chris@305 879 {
Chris@587 880 SVDEBUG << "ImageLayer::checkAddSource(" << img << "): yes, trying..." << endl;
Chris@305 881
Chris@464 882 if (m_fileSources.find(img) != m_fileSources.end()) {
Chris@464 883 return;
Chris@464 884 }
Chris@312 885
Chris@464 886 ProgressDialog dialog(tr("Opening image URL..."), true, 2000);
Chris@464 887 FileSource *rf = new FileSource(img, &dialog);
Chris@464 888 if (rf->isOK()) {
Chris@682 889 cerr << "ok, adding it (local filename = " << rf->getLocalFilename() << ")" << endl;
Chris@464 890 m_fileSources[img] = rf;
Chris@464 891 connect(rf, SIGNAL(ready()), this, SLOT(fileSourceReady()));
Chris@464 892 } else {
Chris@464 893 delete rf;
Chris@305 894 }
Chris@305 895 }
Chris@305 896
Chris@305 897 void
Chris@464 898 ImageLayer::checkAddSources()
Chris@305 899 {
Chris@1469 900 auto model = ModelById::getAs<ImageModel>(m_model);
Chris@1469 901 const EventVector &points(model->getAllEvents());
Chris@305 902
Chris@1438 903 for (EventVector::const_iterator i = points.begin();
Chris@1266 904 i != points.end(); ++i) {
Chris@305 905
Chris@1438 906 checkAddSource((*i).getURI());
Chris@305 907 }
Chris@305 908 }
Chris@305 909
Chris@305 910 void
Chris@464 911 ImageLayer::fileSourceReady()
Chris@305 912 {
Chris@587 913 // SVDEBUG << "ImageLayer::fileSourceReady" << endl;
Chris@305 914
Chris@318 915 FileSource *rf = dynamic_cast<FileSource *>(sender());
Chris@305 916 if (!rf) return;
Chris@305 917
Chris@305 918 QString img;
Chris@464 919 for (FileSourceMap::const_iterator i = m_fileSources.begin();
Chris@464 920 i != m_fileSources.end(); ++i) {
Chris@305 921 if (i->second == rf) {
Chris@305 922 img = i->first;
Chris@682 923 // cerr << "it's image \"" << img << "\"" << endl;
Chris@305 924 break;
Chris@305 925 }
Chris@305 926 }
Chris@305 927 if (img == "") return;
Chris@305 928
Chris@305 929 QMutexLocker locker(&m_imageMapMutex);
Chris@305 930 m_images.erase(img);
Chris@305 931 for (ViewImageMap::iterator i = m_scaled.begin(); i != m_scaled.end(); ++i) {
Chris@305 932 i->second.erase(img);
Chris@1481 933 emit modelChanged(getModel());
Chris@305 934 }
Chris@305 935 }
Chris@305 936
Chris@316 937 void
Chris@316 938 ImageLayer::toXml(QTextStream &stream,
Chris@316 939 QString indent, QString extraAttributes) const
Chris@303 940 {
Chris@316 941 Layer::toXml(stream, indent, extraAttributes);
Chris@303 942 }
Chris@303 943
Chris@303 944 void
Chris@805 945 ImageLayer::setProperties(const QXmlAttributes &)
Chris@303 946 {
Chris@303 947 }
Chris@303 948