annotate layer/ImageLayer.cpp @ 561:aced8ec09bc8

* Complete the overhaul of CSV file import; now you can pick the purpose for each column in the file, and SV should do the rest. The most significant practical improvement here is that we can now handle files in which time and duration do not necessarily appear in known columns.
author Chris Cannam
date Mon, 19 Jul 2010 17:08:56 +0000
parents 69089c9dc42e
children 1fe7951a61e8
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@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@464 610 checkAddSource(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@464 630 std::cerr << "Failed to open image from url \"" << url.toStdString() << "\" (local filename \"" << getLocalFilename(url).toStdString() << "\"" << std::endl;
Chris@464 631 delete m_fileSources[url];
Chris@464 632 m_fileSources.erase(url);
Chris@312 633 return false;
Chris@312 634 }
Chris@312 635
Chris@312 636 ImageModel::Point point(frame, url, "");
Chris@312 637 ImageModel::EditCommand *command =
Chris@312 638 new ImageModel::EditCommand(m_model, "Add Image");
Chris@312 639 command->addPoint(point);
Chris@376 640 finish(command);
Chris@312 641 return true;
Chris@312 642 }
Chris@312 643
Chris@303 644 void
Chris@303 645 ImageLayer::editStart(View *v, QMouseEvent *e)
Chris@303 646 {
Chris@303 647 // std::cerr << "ImageLayer::editStart(" << e->x() << "," << e->y() << ")" << std::endl;
Chris@303 648
Chris@303 649 if (!m_model) return;
Chris@303 650
Chris@303 651 ImageModel::PointList points = getLocalPoints(v, e->x(), e->y());
Chris@303 652 if (points.empty()) return;
Chris@303 653
Chris@303 654 m_editOrigin = e->pos();
Chris@303 655 m_editingPoint = *points.begin();
Chris@303 656 m_originalPoint = m_editingPoint;
Chris@303 657
Chris@303 658 if (m_editingCommand) {
Chris@376 659 finish(m_editingCommand);
Chris@303 660 m_editingCommand = 0;
Chris@303 661 }
Chris@303 662
Chris@303 663 m_editing = true;
Chris@303 664 }
Chris@303 665
Chris@303 666 void
Chris@303 667 ImageLayer::editDrag(View *v, QMouseEvent *e)
Chris@303 668 {
Chris@303 669 if (!m_model || !m_editing) return;
Chris@303 670
Chris@303 671 long frameDiff = v->getFrameForX(e->x()) - v->getFrameForX(m_editOrigin.x());
Chris@303 672 long frame = m_originalPoint.frame + frameDiff;
Chris@303 673
Chris@303 674 if (frame < 0) frame = 0;
Chris@303 675 frame = (frame / m_model->getResolution()) * m_model->getResolution();
Chris@303 676
Chris@303 677 if (!m_editingCommand) {
Chris@303 678 m_editingCommand = new ImageModel::EditCommand(m_model, tr("Move Image"));
Chris@303 679 }
Chris@303 680
Chris@303 681 m_editingCommand->deletePoint(m_editingPoint);
Chris@303 682 m_editingPoint.frame = frame;
Chris@303 683 m_editingCommand->addPoint(m_editingPoint);
Chris@303 684 }
Chris@303 685
Chris@303 686 void
Chris@303 687 ImageLayer::editEnd(View *, QMouseEvent *)
Chris@303 688 {
Chris@303 689 // std::cerr << "ImageLayer::editEnd(" << e->x() << "," << e->y() << ")" << std::endl;
Chris@303 690 if (!m_model || !m_editing) return;
Chris@303 691
Chris@303 692 if (m_editingCommand) {
Chris@376 693 finish(m_editingCommand);
Chris@303 694 }
Chris@303 695
Chris@303 696 m_editingCommand = 0;
Chris@303 697 m_editing = false;
Chris@303 698 }
Chris@303 699
Chris@303 700 bool
Chris@303 701 ImageLayer::editOpen(View *v, QMouseEvent *e)
Chris@303 702 {
Chris@303 703 if (!m_model) return false;
Chris@303 704
Chris@303 705 ImageModel::PointList points = getLocalPoints(v, e->x(), e->y());
Chris@303 706 if (points.empty()) return false;
Chris@303 707
Chris@303 708 QString image = points.begin()->image;
Chris@303 709 QString label = points.begin()->label;
Chris@303 710
Chris@303 711 ImageDialog dialog(tr("Select image"),
Chris@303 712 image,
Chris@303 713 label);
Chris@303 714
Chris@303 715 if (dialog.exec() == QDialog::Accepted) {
Chris@305 716
Chris@464 717 checkAddSource(dialog.getImage());
Chris@305 718
Chris@303 719 ImageModel::ChangeImageCommand *command =
Chris@303 720 new ImageModel::ChangeImageCommand
Chris@303 721 (m_model, *points.begin(), dialog.getImage(), dialog.getLabel());
Chris@305 722
Chris@303 723 CommandHistory::getInstance()->addCommand(command);
Chris@303 724 }
Chris@303 725
Chris@303 726 return true;
Chris@303 727 }
Chris@303 728
Chris@303 729 void
Chris@303 730 ImageLayer::moveSelection(Selection s, size_t newStartFrame)
Chris@303 731 {
Chris@303 732 if (!m_model) return;
Chris@303 733
Chris@303 734 ImageModel::EditCommand *command =
Chris@303 735 new ImageModel::EditCommand(m_model, tr("Drag Selection"));
Chris@303 736
Chris@303 737 ImageModel::PointList points =
Chris@303 738 m_model->getPoints(s.getStartFrame(), s.getEndFrame());
Chris@303 739
Chris@303 740 for (ImageModel::PointList::iterator i = points.begin();
Chris@303 741 i != points.end(); ++i) {
Chris@303 742
Chris@303 743 if (s.contains(i->frame)) {
Chris@303 744 ImageModel::Point newPoint(*i);
Chris@303 745 newPoint.frame = i->frame + newStartFrame - s.getStartFrame();
Chris@303 746 command->deletePoint(*i);
Chris@303 747 command->addPoint(newPoint);
Chris@303 748 }
Chris@303 749 }
Chris@303 750
Chris@376 751 finish(command);
Chris@303 752 }
Chris@303 753
Chris@303 754 void
Chris@303 755 ImageLayer::resizeSelection(Selection s, Selection newSize)
Chris@303 756 {
Chris@303 757 if (!m_model) return;
Chris@303 758
Chris@303 759 ImageModel::EditCommand *command =
Chris@303 760 new ImageModel::EditCommand(m_model, tr("Resize Selection"));
Chris@303 761
Chris@303 762 ImageModel::PointList points =
Chris@303 763 m_model->getPoints(s.getStartFrame(), s.getEndFrame());
Chris@303 764
Chris@303 765 double ratio =
Chris@303 766 double(newSize.getEndFrame() - newSize.getStartFrame()) /
Chris@303 767 double(s.getEndFrame() - s.getStartFrame());
Chris@303 768
Chris@303 769 for (ImageModel::PointList::iterator i = points.begin();
Chris@303 770 i != points.end(); ++i) {
Chris@303 771
Chris@303 772 if (s.contains(i->frame)) {
Chris@303 773
Chris@303 774 double target = i->frame;
Chris@303 775 target = newSize.getStartFrame() +
Chris@303 776 double(target - s.getStartFrame()) * ratio;
Chris@303 777
Chris@303 778 ImageModel::Point newPoint(*i);
Chris@303 779 newPoint.frame = lrint(target);
Chris@303 780 command->deletePoint(*i);
Chris@303 781 command->addPoint(newPoint);
Chris@303 782 }
Chris@303 783 }
Chris@303 784
Chris@376 785 finish(command);
Chris@303 786 }
Chris@303 787
Chris@303 788 void
Chris@303 789 ImageLayer::deleteSelection(Selection s)
Chris@303 790 {
Chris@303 791 if (!m_model) return;
Chris@303 792
Chris@303 793 ImageModel::EditCommand *command =
Chris@303 794 new ImageModel::EditCommand(m_model, tr("Delete Selection"));
Chris@303 795
Chris@303 796 ImageModel::PointList points =
Chris@303 797 m_model->getPoints(s.getStartFrame(), s.getEndFrame());
Chris@303 798
Chris@303 799 for (ImageModel::PointList::iterator i = points.begin();
Chris@303 800 i != points.end(); ++i) {
Chris@303 801 if (s.contains(i->frame)) command->deletePoint(*i);
Chris@303 802 }
Chris@303 803
Chris@376 804 finish(command);
Chris@303 805 }
Chris@303 806
Chris@303 807 void
Chris@359 808 ImageLayer::copy(View *v, Selection s, Clipboard &to)
Chris@303 809 {
Chris@303 810 if (!m_model) return;
Chris@303 811
Chris@303 812 ImageModel::PointList points =
Chris@303 813 m_model->getPoints(s.getStartFrame(), s.getEndFrame());
Chris@303 814
Chris@303 815 for (ImageModel::PointList::iterator i = points.begin();
Chris@303 816 i != points.end(); ++i) {
Chris@303 817 if (s.contains(i->frame)) {
Chris@303 818 Clipboard::Point point(i->frame, i->label);
Chris@360 819 point.setReferenceFrame(alignToReference(v, i->frame));
Chris@303 820 to.addPoint(point);
Chris@303 821 }
Chris@303 822 }
Chris@303 823 }
Chris@303 824
Chris@303 825 bool
Chris@359 826 ImageLayer::paste(View *v, const Clipboard &from, int frameOffset, bool /* interactive */)
Chris@303 827 {
Chris@303 828 if (!m_model) return false;
Chris@303 829
Chris@303 830 const Clipboard::PointList &points = from.getPoints();
Chris@303 831
Chris@360 832 bool realign = false;
Chris@360 833
Chris@360 834 if (clipboardHasDifferentAlignment(v, from)) {
Chris@360 835
Chris@360 836 QMessageBox::StandardButton button =
Chris@360 837 QMessageBox::question(v, tr("Re-align pasted items?"),
Chris@360 838 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 839 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
Chris@360 840 QMessageBox::Yes);
Chris@360 841
Chris@360 842 if (button == QMessageBox::Cancel) {
Chris@360 843 return false;
Chris@360 844 }
Chris@360 845
Chris@360 846 if (button == QMessageBox::Yes) {
Chris@360 847 realign = true;
Chris@360 848 }
Chris@360 849 }
Chris@360 850
Chris@303 851 ImageModel::EditCommand *command =
Chris@303 852 new ImageModel::EditCommand(m_model, tr("Paste"));
Chris@303 853
Chris@303 854 for (Clipboard::PointList::const_iterator i = points.begin();
Chris@303 855 i != points.end(); ++i) {
Chris@303 856
Chris@303 857 if (!i->haveFrame()) continue;
Chris@360 858
Chris@303 859 size_t frame = 0;
Chris@360 860
Chris@360 861 if (!realign) {
Chris@360 862
Chris@360 863 frame = i->getFrame();
Chris@360 864
Chris@360 865 } else {
Chris@360 866
Chris@360 867 if (i->haveReferenceFrame()) {
Chris@360 868 frame = i->getReferenceFrame();
Chris@360 869 frame = alignFromReference(v, frame);
Chris@360 870 } else {
Chris@360 871 frame = i->getFrame();
Chris@360 872 }
Chris@303 873 }
Chris@360 874
Chris@303 875 ImageModel::Point newPoint(frame);
Chris@303 876
Chris@303 877 //!!! inadequate
Chris@303 878
Chris@303 879 if (i->haveLabel()) {
Chris@303 880 newPoint.label = i->getLabel();
Chris@303 881 } else if (i->haveValue()) {
Chris@303 882 newPoint.label = QString("%1").arg(i->getValue());
Chris@303 883 } else {
Chris@303 884 newPoint.label = tr("New Point");
Chris@303 885 }
Chris@303 886
Chris@303 887 command->addPoint(newPoint);
Chris@303 888 }
Chris@303 889
Chris@376 890 finish(command);
Chris@303 891 return true;
Chris@303 892 }
Chris@303 893
Chris@303 894 QString
Chris@305 895 ImageLayer::getLocalFilename(QString img) const
Chris@305 896 {
Chris@464 897 if (m_fileSources.find(img) == m_fileSources.end()) {
Chris@464 898 checkAddSource(img);
Chris@464 899 if (m_fileSources.find(img) == m_fileSources.end()) {
Chris@312 900 return img;
Chris@312 901 }
Chris@305 902 }
Chris@464 903 return m_fileSources[img]->getLocalFilename();
Chris@305 904 }
Chris@305 905
Chris@305 906 void
Chris@464 907 ImageLayer::checkAddSource(QString img) const
Chris@305 908 {
Chris@464 909 std::cerr << "ImageLayer::checkAddSource(" << img.toStdString() << "): yes, trying..." << std::endl;
Chris@305 910
Chris@464 911 if (m_fileSources.find(img) != m_fileSources.end()) {
Chris@464 912 return;
Chris@464 913 }
Chris@312 914
Chris@464 915 ProgressDialog dialog(tr("Opening image URL..."), true, 2000);
Chris@464 916 FileSource *rf = new FileSource(img, &dialog);
Chris@464 917 if (rf->isOK()) {
Chris@464 918 std::cerr << "ok, adding it (local filename = " << rf->getLocalFilename().toStdString() << ")" << std::endl;
Chris@464 919 m_fileSources[img] = rf;
Chris@464 920 connect(rf, SIGNAL(ready()), this, SLOT(fileSourceReady()));
Chris@464 921 } else {
Chris@464 922 delete rf;
Chris@305 923 }
Chris@305 924 }
Chris@305 925
Chris@305 926 void
Chris@464 927 ImageLayer::checkAddSources()
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@464 934 checkAddSource((*i).image);
Chris@305 935 }
Chris@305 936 }
Chris@305 937
Chris@305 938 void
Chris@464 939 ImageLayer::fileSourceReady()
Chris@305 940 {
Chris@464 941 // std::cerr << "ImageLayer::fileSourceReady" << 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@464 947 for (FileSourceMap::const_iterator i = m_fileSources.begin();
Chris@464 948 i != m_fileSources.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