annotate layer/ImageLayer.cpp @ 314:7b96a3261e1c

* "Align mode"
author Chris Cannam
date Tue, 16 Oct 2007 18:42:51 +0000
parents 6de6f78b13a1
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