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