annotate layer/ImageLayer.cpp @ 378:22b72f0f6a4e

* More work to abstract out interactive components used in the data library, so that it does not need to depend on QtGui.
author Chris Cannam
date Fri, 14 Mar 2008 17:14:21 +0000
parents e1a9e478b7f2
children 69089c9dc42e
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@318 57 for (FileSourceMap::iterator i = m_remoteFiles.begin();
Chris@305 58 i != m_remoteFiles.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@303 81 ImageLayer::getPropertyLabel(const PropertyName &name) 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@303 113 ImageLayer::getValueExtents(float &, float &, bool &, QString &) const
Chris@303 114 {
Chris@303 115 return false;
Chris@303 116 }
Chris@303 117
Chris@303 118 bool
Chris@303 119 ImageLayer::isLayerScrollable(const View *v) const
Chris@303 120 {
Chris@304 121 return true;
Chris@303 122 }
Chris@303 123
Chris@303 124
Chris@303 125 ImageModel::PointList
Chris@303 126 ImageLayer::getLocalPoints(View *v, int x, int y) const
Chris@303 127 {
Chris@303 128 if (!m_model) return ImageModel::PointList();
Chris@303 129
Chris@304 130 // std::cerr << "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@305 158 // std::cerr << "scaled width = " << width << std::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@304 166 // std::cerr << rv.size() << " point(s)" << std::endl;
Chris@303 167
Chris@303 168 return rv;
Chris@303 169 }
Chris@303 170
Chris@303 171 QString
Chris@303 172 ImageLayer::getFeatureDescription(View *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@303 188 long useFrame = points.begin()->frame;
Chris@303 189
Chris@303 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@303 211 ImageLayer::snapToFeatureFrame(View *v, int &frame,
Chris@303 212 size_t &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@303 231 int 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@303 283 ImageLayer::paint(View *v, QPainter &paint, QRect rect) const
Chris@303 284 {
Chris@303 285 if (!m_model || !m_model->isOK()) return;
Chris@303 286
Chris@303 287 int 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@304 293 int x0 = 0, x1 = v->width();
Chris@304 294
Chris@303 295 long frame0 = v->getFrameForX(x0);
Chris@303 296 long 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@304 302 paint.setClipRect(rect.x(), 0, rect.width(), v->height());
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@304 341 ImageLayer::drawImage(View *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@304 361 if (v->height() < 100) {
Chris@304 362 topMargin = 5;
Chris@304 363 bottomMargin = 5;
Chris@304 364 }
Chris@304 365
Chris@304 366 int maxBoxHeight = v->height() - 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@304 375 int likelyHeight = v->height() / 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@304 438 bottomMargin = v->height() - topMargin - boxHeight;
Chris@304 439 if (bottomMargin > topMargin + v->height()/7) {
Chris@304 440 topMargin += v->height()/8;
Chris@304 441 bottomMargin -= v->height()/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@303 483 ImageLayer::setLayerDormant(const View *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@305 503 // std::cerr << "getImageOriginalSize: \"" << name.toStdString() << "\"" << std::endl;
Chris@305 504
Chris@305 505 QMutexLocker locker(&m_imageMapMutex);
Chris@303 506 if (m_images.find(name) == m_images.end()) {
Chris@305 507 // std::cerr << "don't have, trying to open local" << std::endl;
Chris@305 508 m_images[name] = QImage(getLocalFilename(name));
Chris@303 509 }
Chris@304 510 if (m_images[name].isNull()) {
Chris@305 511 // std::cerr << "null image" << std::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@303 520 ImageLayer::getImage(View *v, QString name, QSize maxSize) const
Chris@303 521 {
Chris@303 522 bool need = false;
Chris@303 523
Chris@305 524 // std::cerr << "ImageLayer::getImage(" << v << ", " << name.toStdString() << ", ("
Chris@305 525 // << maxSize.width() << "x" << maxSize.height() << "))" << std::endl;
Chris@303 526
Chris@304 527 if (!m_scaled[v][name].isNull() &&
Chris@304 528 ((m_scaled[v][name].width() == maxSize.width() &&
Chris@304 529 m_scaled[v][name].height() <= maxSize.height()) ||
Chris@304 530 (m_scaled[v][name].width() <= maxSize.width() &&
Chris@304 531 m_scaled[v][name].height() == maxSize.height()))) {
Chris@305 532 // std::cerr << "cache hit" << std::endl;
Chris@303 533 return m_scaled[v][name];
Chris@303 534 }
Chris@303 535
Chris@305 536 QMutexLocker locker(&m_imageMapMutex);
Chris@305 537
Chris@303 538 if (m_images.find(name) == m_images.end()) {
Chris@305 539 m_images[name] = QImage(getLocalFilename(name));
Chris@303 540 }
Chris@303 541
Chris@303 542 if (m_images[name].isNull()) {
Chris@305 543 // std::cerr << "null image" << std::endl;
Chris@303 544 m_scaled[v][name] = QImage();
Chris@304 545 } else if (m_images[name].width() <= maxSize.width() &&
Chris@304 546 m_images[name].height() <= maxSize.height()) {
Chris@304 547 m_scaled[v][name] = m_images[name];
Chris@303 548 } else {
Chris@303 549 m_scaled[v][name] =
Chris@303 550 m_images[name].scaled(maxSize,
Chris@303 551 Qt::KeepAspectRatio,
Chris@303 552 Qt::SmoothTransformation);
Chris@303 553 }
Chris@303 554
Chris@303 555 return m_scaled[v][name];
Chris@303 556 }
Chris@303 557
Chris@303 558 void
Chris@303 559 ImageLayer::drawStart(View *v, QMouseEvent *e)
Chris@303 560 {
Chris@303 561 // std::cerr << "ImageLayer::drawStart(" << e->x() << "," << e->y() << ")" << std::endl;
Chris@303 562
Chris@303 563 if (!m_model) {
Chris@303 564 std::cerr << "ImageLayer::drawStart: no model" << std::endl;
Chris@303 565 return;
Chris@303 566 }
Chris@303 567
Chris@303 568 long frame = v->getFrameForX(e->x());
Chris@303 569 if (frame < 0) frame = 0;
Chris@303 570 frame = frame / m_model->getResolution() * m_model->getResolution();
Chris@303 571
Chris@303 572 m_editingPoint = ImageModel::Point(frame, "", "");
Chris@303 573 m_originalPoint = m_editingPoint;
Chris@303 574
Chris@376 575 if (m_editingCommand) finish(m_editingCommand);
Chris@303 576 m_editingCommand = new ImageModel::EditCommand(m_model, "Add Image");
Chris@303 577 m_editingCommand->addPoint(m_editingPoint);
Chris@303 578
Chris@303 579 m_editing = true;
Chris@303 580 }
Chris@303 581
Chris@303 582 void
Chris@303 583 ImageLayer::drawDrag(View *v, QMouseEvent *e)
Chris@303 584 {
Chris@303 585 // std::cerr << "ImageLayer::drawDrag(" << e->x() << "," << e->y() << ")" << std::endl;
Chris@303 586
Chris@303 587 if (!m_model || !m_editing) return;
Chris@303 588
Chris@303 589 long frame = v->getFrameForX(e->x());
Chris@303 590 if (frame < 0) frame = 0;
Chris@303 591 frame = frame / m_model->getResolution() * m_model->getResolution();
Chris@303 592
Chris@303 593 m_editingCommand->deletePoint(m_editingPoint);
Chris@303 594 m_editingPoint.frame = frame;
Chris@303 595 m_editingCommand->addPoint(m_editingPoint);
Chris@303 596 }
Chris@303 597
Chris@303 598 void
Chris@303 599 ImageLayer::drawEnd(View *v, QMouseEvent *)
Chris@303 600 {
Chris@303 601 // std::cerr << "ImageLayer::drawEnd(" << e->x() << "," << e->y() << ")" << std::endl;
Chris@303 602 if (!m_model || !m_editing) return;
Chris@303 603
Chris@303 604 bool ok = false;
Chris@303 605
Chris@307 606 ImageDialog dialog(tr("Select image"), "", "");
Chris@305 607
Chris@303 608 if (dialog.exec() == QDialog::Accepted) {
Chris@305 609
Chris@305 610 checkAddRemote(dialog.getImage());
Chris@305 611
Chris@303 612 ImageModel::ChangeImageCommand *command =
Chris@303 613 new ImageModel::ChangeImageCommand
Chris@303 614 (m_model, m_editingPoint, dialog.getImage(), dialog.getLabel());
Chris@303 615 m_editingCommand->addCommand(command);
Chris@307 616 } else {
Chris@307 617 m_editingCommand->deletePoint(m_editingPoint);
Chris@303 618 }
Chris@303 619
Chris@376 620 finish(m_editingCommand);
Chris@303 621 m_editingCommand = 0;
Chris@303 622 m_editing = false;
Chris@303 623 }
Chris@303 624
Chris@312 625 bool
Chris@312 626 ImageLayer::addImage(long frame, QString url)
Chris@312 627 {
Chris@312 628 QImage image(getLocalFilename(url));
Chris@312 629 if (image.isNull()) {
Chris@312 630 delete m_remoteFiles[url];
Chris@312 631 m_remoteFiles.erase(url);
Chris@312 632 return false;
Chris@312 633 }
Chris@312 634
Chris@312 635 ImageModel::Point point(frame, url, "");
Chris@312 636 ImageModel::EditCommand *command =
Chris@312 637 new ImageModel::EditCommand(m_model, "Add Image");
Chris@312 638 command->addPoint(point);
Chris@376 639 finish(command);
Chris@312 640 return true;
Chris@312 641 }
Chris@312 642
Chris@303 643 void
Chris@303 644 ImageLayer::editStart(View *v, QMouseEvent *e)
Chris@303 645 {
Chris@303 646 // std::cerr << "ImageLayer::editStart(" << e->x() << "," << e->y() << ")" << std::endl;
Chris@303 647
Chris@303 648 if (!m_model) return;
Chris@303 649
Chris@303 650 ImageModel::PointList points = getLocalPoints(v, e->x(), e->y());
Chris@303 651 if (points.empty()) return;
Chris@303 652
Chris@303 653 m_editOrigin = e->pos();
Chris@303 654 m_editingPoint = *points.begin();
Chris@303 655 m_originalPoint = m_editingPoint;
Chris@303 656
Chris@303 657 if (m_editingCommand) {
Chris@376 658 finish(m_editingCommand);
Chris@303 659 m_editingCommand = 0;
Chris@303 660 }
Chris@303 661
Chris@303 662 m_editing = true;
Chris@303 663 }
Chris@303 664
Chris@303 665 void
Chris@303 666 ImageLayer::editDrag(View *v, QMouseEvent *e)
Chris@303 667 {
Chris@303 668 if (!m_model || !m_editing) return;
Chris@303 669
Chris@303 670 long frameDiff = v->getFrameForX(e->x()) - v->getFrameForX(m_editOrigin.x());
Chris@303 671 long frame = m_originalPoint.frame + frameDiff;
Chris@303 672
Chris@303 673 if (frame < 0) frame = 0;
Chris@303 674 frame = (frame / m_model->getResolution()) * m_model->getResolution();
Chris@303 675
Chris@303 676 if (!m_editingCommand) {
Chris@303 677 m_editingCommand = new ImageModel::EditCommand(m_model, tr("Move Image"));
Chris@303 678 }
Chris@303 679
Chris@303 680 m_editingCommand->deletePoint(m_editingPoint);
Chris@303 681 m_editingPoint.frame = frame;
Chris@303 682 m_editingCommand->addPoint(m_editingPoint);
Chris@303 683 }
Chris@303 684
Chris@303 685 void
Chris@303 686 ImageLayer::editEnd(View *, QMouseEvent *)
Chris@303 687 {
Chris@303 688 // std::cerr << "ImageLayer::editEnd(" << e->x() << "," << e->y() << ")" << std::endl;
Chris@303 689 if (!m_model || !m_editing) return;
Chris@303 690
Chris@303 691 if (m_editingCommand) {
Chris@376 692 finish(m_editingCommand);
Chris@303 693 }
Chris@303 694
Chris@303 695 m_editingCommand = 0;
Chris@303 696 m_editing = false;
Chris@303 697 }
Chris@303 698
Chris@303 699 bool
Chris@303 700 ImageLayer::editOpen(View *v, QMouseEvent *e)
Chris@303 701 {
Chris@303 702 if (!m_model) return false;
Chris@303 703
Chris@303 704 ImageModel::PointList points = getLocalPoints(v, e->x(), e->y());
Chris@303 705 if (points.empty()) return false;
Chris@303 706
Chris@303 707 QString image = points.begin()->image;
Chris@303 708 QString label = points.begin()->label;
Chris@303 709
Chris@303 710 ImageDialog dialog(tr("Select image"),
Chris@303 711 image,
Chris@303 712 label);
Chris@303 713
Chris@303 714 if (dialog.exec() == QDialog::Accepted) {
Chris@305 715
Chris@305 716 checkAddRemote(dialog.getImage());
Chris@305 717
Chris@303 718 ImageModel::ChangeImageCommand *command =
Chris@303 719 new ImageModel::ChangeImageCommand
Chris@303 720 (m_model, *points.begin(), dialog.getImage(), dialog.getLabel());
Chris@305 721
Chris@303 722 CommandHistory::getInstance()->addCommand(command);
Chris@303 723 }
Chris@303 724
Chris@303 725 return true;
Chris@303 726 }
Chris@303 727
Chris@303 728 void
Chris@303 729 ImageLayer::moveSelection(Selection s, size_t newStartFrame)
Chris@303 730 {
Chris@303 731 if (!m_model) return;
Chris@303 732
Chris@303 733 ImageModel::EditCommand *command =
Chris@303 734 new ImageModel::EditCommand(m_model, tr("Drag Selection"));
Chris@303 735
Chris@303 736 ImageModel::PointList points =
Chris@303 737 m_model->getPoints(s.getStartFrame(), s.getEndFrame());
Chris@303 738
Chris@303 739 for (ImageModel::PointList::iterator i = points.begin();
Chris@303 740 i != points.end(); ++i) {
Chris@303 741
Chris@303 742 if (s.contains(i->frame)) {
Chris@303 743 ImageModel::Point newPoint(*i);
Chris@303 744 newPoint.frame = i->frame + newStartFrame - s.getStartFrame();
Chris@303 745 command->deletePoint(*i);
Chris@303 746 command->addPoint(newPoint);
Chris@303 747 }
Chris@303 748 }
Chris@303 749
Chris@376 750 finish(command);
Chris@303 751 }
Chris@303 752
Chris@303 753 void
Chris@303 754 ImageLayer::resizeSelection(Selection s, Selection newSize)
Chris@303 755 {
Chris@303 756 if (!m_model) return;
Chris@303 757
Chris@303 758 ImageModel::EditCommand *command =
Chris@303 759 new ImageModel::EditCommand(m_model, tr("Resize Selection"));
Chris@303 760
Chris@303 761 ImageModel::PointList points =
Chris@303 762 m_model->getPoints(s.getStartFrame(), s.getEndFrame());
Chris@303 763
Chris@303 764 double ratio =
Chris@303 765 double(newSize.getEndFrame() - newSize.getStartFrame()) /
Chris@303 766 double(s.getEndFrame() - s.getStartFrame());
Chris@303 767
Chris@303 768 for (ImageModel::PointList::iterator i = points.begin();
Chris@303 769 i != points.end(); ++i) {
Chris@303 770
Chris@303 771 if (s.contains(i->frame)) {
Chris@303 772
Chris@303 773 double target = i->frame;
Chris@303 774 target = newSize.getStartFrame() +
Chris@303 775 double(target - s.getStartFrame()) * ratio;
Chris@303 776
Chris@303 777 ImageModel::Point newPoint(*i);
Chris@303 778 newPoint.frame = lrint(target);
Chris@303 779 command->deletePoint(*i);
Chris@303 780 command->addPoint(newPoint);
Chris@303 781 }
Chris@303 782 }
Chris@303 783
Chris@376 784 finish(command);
Chris@303 785 }
Chris@303 786
Chris@303 787 void
Chris@303 788 ImageLayer::deleteSelection(Selection s)
Chris@303 789 {
Chris@303 790 if (!m_model) return;
Chris@303 791
Chris@303 792 ImageModel::EditCommand *command =
Chris@303 793 new ImageModel::EditCommand(m_model, tr("Delete Selection"));
Chris@303 794
Chris@303 795 ImageModel::PointList points =
Chris@303 796 m_model->getPoints(s.getStartFrame(), s.getEndFrame());
Chris@303 797
Chris@303 798 for (ImageModel::PointList::iterator i = points.begin();
Chris@303 799 i != points.end(); ++i) {
Chris@303 800 if (s.contains(i->frame)) command->deletePoint(*i);
Chris@303 801 }
Chris@303 802
Chris@376 803 finish(command);
Chris@303 804 }
Chris@303 805
Chris@303 806 void
Chris@359 807 ImageLayer::copy(View *v, Selection s, Clipboard &to)
Chris@303 808 {
Chris@303 809 if (!m_model) return;
Chris@303 810
Chris@303 811 ImageModel::PointList points =
Chris@303 812 m_model->getPoints(s.getStartFrame(), s.getEndFrame());
Chris@303 813
Chris@303 814 for (ImageModel::PointList::iterator i = points.begin();
Chris@303 815 i != points.end(); ++i) {
Chris@303 816 if (s.contains(i->frame)) {
Chris@303 817 Clipboard::Point point(i->frame, i->label);
Chris@360 818 point.setReferenceFrame(alignToReference(v, i->frame));
Chris@303 819 to.addPoint(point);
Chris@303 820 }
Chris@303 821 }
Chris@303 822 }
Chris@303 823
Chris@303 824 bool
Chris@359 825 ImageLayer::paste(View *v, const Clipboard &from, int frameOffset, bool /* interactive */)
Chris@303 826 {
Chris@303 827 if (!m_model) return false;
Chris@303 828
Chris@303 829 const Clipboard::PointList &points = from.getPoints();
Chris@303 830
Chris@360 831 bool realign = false;
Chris@360 832
Chris@360 833 if (clipboardHasDifferentAlignment(v, from)) {
Chris@360 834
Chris@360 835 QMessageBox::StandardButton button =
Chris@360 836 QMessageBox::question(v, tr("Re-align pasted items?"),
Chris@360 837 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 838 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
Chris@360 839 QMessageBox::Yes);
Chris@360 840
Chris@360 841 if (button == QMessageBox::Cancel) {
Chris@360 842 return false;
Chris@360 843 }
Chris@360 844
Chris@360 845 if (button == QMessageBox::Yes) {
Chris@360 846 realign = true;
Chris@360 847 }
Chris@360 848 }
Chris@360 849
Chris@303 850 ImageModel::EditCommand *command =
Chris@303 851 new ImageModel::EditCommand(m_model, tr("Paste"));
Chris@303 852
Chris@303 853 for (Clipboard::PointList::const_iterator i = points.begin();
Chris@303 854 i != points.end(); ++i) {
Chris@303 855
Chris@303 856 if (!i->haveFrame()) continue;
Chris@360 857
Chris@303 858 size_t frame = 0;
Chris@360 859
Chris@360 860 if (!realign) {
Chris@360 861
Chris@360 862 frame = i->getFrame();
Chris@360 863
Chris@360 864 } else {
Chris@360 865
Chris@360 866 if (i->haveReferenceFrame()) {
Chris@360 867 frame = i->getReferenceFrame();
Chris@360 868 frame = alignFromReference(v, frame);
Chris@360 869 } else {
Chris@360 870 frame = i->getFrame();
Chris@360 871 }
Chris@303 872 }
Chris@360 873
Chris@303 874 ImageModel::Point newPoint(frame);
Chris@303 875
Chris@303 876 //!!! inadequate
Chris@303 877
Chris@303 878 if (i->haveLabel()) {
Chris@303 879 newPoint.label = i->getLabel();
Chris@303 880 } else if (i->haveValue()) {
Chris@303 881 newPoint.label = QString("%1").arg(i->getValue());
Chris@303 882 } else {
Chris@303 883 newPoint.label = tr("New Point");
Chris@303 884 }
Chris@303 885
Chris@303 886 command->addPoint(newPoint);
Chris@303 887 }
Chris@303 888
Chris@376 889 finish(command);
Chris@303 890 return true;
Chris@303 891 }
Chris@303 892
Chris@303 893 QString
Chris@305 894 ImageLayer::getLocalFilename(QString img) const
Chris@305 895 {
Chris@305 896 if (m_remoteFiles.find(img) == m_remoteFiles.end()) {
Chris@305 897 checkAddRemote(img);
Chris@312 898 if (m_remoteFiles.find(img) == m_remoteFiles.end()) {
Chris@312 899 return img;
Chris@312 900 }
Chris@305 901 }
Chris@305 902 return m_remoteFiles[img]->getLocalFilename();
Chris@305 903 }
Chris@305 904
Chris@305 905 void
Chris@305 906 ImageLayer::checkAddRemote(QString img) const
Chris@305 907 {
Chris@318 908 if (FileSource::isRemote(img)) {
Chris@305 909
Chris@312 910 std::cerr << "ImageLayer::checkAddRemote(" << img.toStdString() << "): yes, trying..." << std::endl;
Chris@312 911
Chris@305 912 if (m_remoteFiles.find(img) != m_remoteFiles.end()) {
Chris@305 913 return;
Chris@305 914 }
Chris@305 915
Chris@378 916 ProgressDialog dialog(tr("Opening image URL..."), true, 2000);
Chris@378 917 FileSource *rf = new FileSource(img, &dialog);
Chris@317 918 if (rf->isOK()) {
Chris@317 919 std::cerr << "ok, adding it (local filename = " << rf->getLocalFilename().toStdString() << ")" << std::endl;
Chris@317 920 m_remoteFiles[img] = rf;
Chris@317 921 connect(rf, SIGNAL(ready()), this, SLOT(remoteFileReady()));
Chris@317 922 } else {
Chris@317 923 delete rf;
Chris@305 924 }
Chris@305 925 }
Chris@305 926 }
Chris@305 927
Chris@305 928 void
Chris@305 929 ImageLayer::checkAddRemotes()
Chris@305 930 {
Chris@305 931 const ImageModel::PointList &points(m_model->getPoints());
Chris@305 932
Chris@305 933 for (ImageModel::PointList::const_iterator i = points.begin();
Chris@305 934 i != points.end(); ++i) {
Chris@305 935
Chris@305 936 checkAddRemote((*i).image);
Chris@305 937 }
Chris@305 938 }
Chris@305 939
Chris@305 940 void
Chris@305 941 ImageLayer::remoteFileReady()
Chris@305 942 {
Chris@305 943 // std::cerr << "ImageLayer::remoteFileReady" << std::endl;
Chris@305 944
Chris@318 945 FileSource *rf = dynamic_cast<FileSource *>(sender());
Chris@305 946 if (!rf) return;
Chris@305 947
Chris@305 948 QString img;
Chris@318 949 for (FileSourceMap::const_iterator i = m_remoteFiles.begin();
Chris@305 950 i != m_remoteFiles.end(); ++i) {
Chris@305 951 if (i->second == rf) {
Chris@305 952 img = i->first;
Chris@305 953 // std::cerr << "it's image \"" << img.toStdString() << "\"" << std::endl;
Chris@305 954 break;
Chris@305 955 }
Chris@305 956 }
Chris@305 957 if (img == "") return;
Chris@305 958
Chris@305 959 QMutexLocker locker(&m_imageMapMutex);
Chris@305 960 m_images.erase(img);
Chris@305 961 for (ViewImageMap::iterator i = m_scaled.begin(); i != m_scaled.end(); ++i) {
Chris@305 962 i->second.erase(img);
Chris@306 963 emit modelChanged();
Chris@305 964 }
Chris@305 965 }
Chris@305 966
Chris@316 967 void
Chris@316 968 ImageLayer::toXml(QTextStream &stream,
Chris@316 969 QString indent, QString extraAttributes) const
Chris@303 970 {
Chris@316 971 Layer::toXml(stream, indent, extraAttributes);
Chris@303 972 }
Chris@303 973
Chris@303 974 void
Chris@303 975 ImageLayer::setProperties(const QXmlAttributes &attributes)
Chris@303 976 {
Chris@303 977 }
Chris@303 978