annotate layer/ImageLayer.cpp @ 1460:69b7fdd6394f

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