annotate layer/ImageLayer.cpp @ 317:e251c3599ea8

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