annotate layer/ImageLayer.cpp @ 1088:c520f90bbf2e spectrogram-minor-refactor

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