annotate layer/ImageLayer.cpp @ 364:f1e6204c1f17

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