annotate layer/ImageLayer.cpp @ 1509:8145a9c4c253

The default key frame map is not working well at the moment, because its extents are not being properly updated as the models they depend on are loaded. Leave it empty for now.
author Chris Cannam
date Tue, 17 Sep 2019 12:50:34 +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