annotate layer/ImageLayer.cpp @ 1612:129c704566ff csv-import-headers

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