annotate layer/ImageLayer.cpp @ 1619:36634b427d61

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