annotate layer/ImageLayer.cpp @ 312:6de6f78b13a1

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