annotate layer/ImageLayer.cpp @ 1447:8afea53332f3 single-point

Add option to make pane sizes auto-resize-only (i.e. remove user control via a splitter); also place alignment views above panes instead of below, meaning the extra bit of space that we currently have for the pane without one at least goes to the primary pane
author Chris Cannam
date Tue, 30 Apr 2019 15:53:21 +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