annotate layer/ImageLayer.cpp @ 1470:696e569ff21b by-id

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