18 #include "data/model/Model.h" 19 #include "base/RealTime.h" 20 #include "base/Profiler.h" 23 #include "data/model/ImageModel.h" 24 #include "data/fileio/FileSource.h" 30 #include <QMouseEvent> 31 #include <QInputDialog> 32 #include <QMutexLocker> 33 #include <QTextStream> 34 #include <QMessageBox> 36 #include <QImageReader> 52 m_editingCommand(nullptr)
67 auto model = ModelById::get(
m_model);
68 if (model)
return model->getCompletion();
75 auto newModel = ModelById::getAs<ImageModel>(modelId);
77 if (!modelId.isNone() && !newModel) {
78 throw std::logic_error(
"Not an ImageModel");
94 return Layer::getProperties();
106 return Layer::getPropertyType(name);
111 int *min,
int *max,
int *deflt)
const 113 return Layer::getPropertyRangeAndValue(name, min, max, deflt);
120 return Layer::getPropertyValueLabel(name, value);
126 Layer::setProperty(name, value);
144 auto model = ModelById::getAs<ImageModel>(
m_model);
145 if (!model)
return {};
148 EventVector points(model->getAllEvents());
152 for (EventVector::const_iterator i = points.begin(); i != points.end(); ) {
159 if (i != points.end()) {
173 width =
m_scaled[v][p.getURI()].width();
177 if (x >= px && x < px + width) {
192 auto model = ModelById::getAs<ImageModel>(
m_model);
193 if (!model || !model->getSampleRate())
return "";
197 if (points.empty()) {
198 if (!model->isReady()) {
199 return tr(
"In progress");
232 auto model = ModelById::getAs<ImageModel>(
m_model);
237 resolution = model->getResolution();
241 if (points.empty())
return false;
242 frame = points.begin()->getFrame();
247 if (model->getNearestEventMatching
249 [](Event) { return true; },
250 snap ==
SnapLeft ? EventSeries::Backward : EventSeries::Forward,
252 frame = e.getFrame();
262 auto model = ModelById::getAs<ImageModel>(
m_model);
263 if (!model || !model->isOK())
return;
265 sv_samplerate_t sampleRate = model->getSampleRate();
266 if (!sampleRate)
return;
276 EventVector points(model->getEventsWithin(frame0, frame1 - frame0, 2));
277 if (points.empty())
return;
280 paint.setClipRect(rect.x(), 0, rect.width(), v->
getPaintHeight());
289 brushColour.getHsv(&h, &s, &val);
290 brushColour.setHsv(h, s, 255, 240);
292 paint.setPen(penColour);
293 paint.setBrush(brushColour);
294 paint.setRenderHint(QPainter::Antialiasing,
true);
296 for (EventVector::const_iterator i = points.begin();
297 i != points.end(); ++i) {
304 EventVector::const_iterator j = i;
306 if (j != points.end()) {
308 if (jx < nx) nx = jx;
314 paint.setRenderHint(QPainter::Antialiasing,
false);
322 QString label = p.getLabel();
323 QString imageName = p.getURI();
326 QString additionalText;
330 image = QImage(
":icons/emptypage.png");
331 imageSize = image.size();
332 additionalText = imageName;
336 int bottomMargin = 10;
344 int maxBoxHeight = v->
getPaintHeight() - topMargin - bottomMargin;
346 int availableWidth = nx - x - 3;
347 if (availableWidth < 20) availableWidth = 20;
356 ((maxBoxHeight - likelyHeight) * imageSize.width())
357 / imageSize.height();
359 if (likelyWidth > imageSize.width()) {
360 likelyWidth = imageSize.width();
363 if (likelyWidth > availableWidth) {
364 likelyWidth = availableWidth;
370 #pragma GCC diagnostic ignored "-Wdeprecated-declarations" 372 int singleWidth = paint.fontMetrics().width(label);
373 if (singleWidth < availableWidth && singleWidth < likelyWidth * 2) {
374 likelyWidth = singleWidth + 4;
377 labelRect = paint.fontMetrics().boundingRect
378 (QRect(0, 0, likelyWidth, likelyHeight),
379 Qt::AlignCenter | Qt::TextWordWrap, label);
381 labelRect.setWidth(labelRect.width() + 6);
384 if (image.isNull()) {
386 QSize(availableWidth,
387 maxBoxHeight - labelRect.height()));
390 int boxWidth = image.width();
391 if (boxWidth < labelRect.width()) {
392 boxWidth = labelRect.width();
395 int boxHeight = image.height();
397 boxHeight += labelRect.height() + spacing;
400 int division = image.height();
402 if (additionalText !=
"") {
406 QFont font(paint.font());
407 font.setItalic(
true);
410 int tw = paint.fontMetrics().width(additionalText);
411 if (tw > availableWidth) {
417 boxHeight += paint.fontMetrics().height();
418 division += paint.fontMetrics().height();
427 paint.drawRect(x - 1,
434 imageY = topMargin + labelRect.height() + spacing;
439 paint.drawImage(x + (boxWidth - image.width())/2,
443 if (additionalText !=
"") {
445 imageY + image.height() + paint.fontMetrics().ascent(),
452 topMargin + labelRect.height() + spacing,
454 topMargin + labelRect.height() + spacing);
456 paint.drawText(QRect(x,
460 Qt::AlignCenter | Qt::TextWordWrap,
473 for (ImageMap::iterator i =
m_scaled[v].begin();
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()))) {
526 }
else if (
m_images[name].width() <= maxSize.width() &&
527 m_images[name].height() <= maxSize.height()) {
533 Qt::SmoothTransformation);
544 auto model = ModelById::getAs<ImageModel>(
m_model);
546 SVDEBUG <<
"ImageLayer::drawStart: no model" << endl;
551 if (frame < 0) frame = 0;
552 frame = frame / model->getResolution() * model->getResolution();
569 auto model = ModelById::getAs<ImageModel>(
m_model);
573 if (frame < 0) frame = 0;
574 frame = frame / model->getResolution() * model->getResolution();
586 auto model = ModelById::getAs<ImageModel>(
m_model);
593 if (dialog.exec() == QDialog::Accepted) {
613 QString ext = QFileInfo(filename).suffix().toLower();
614 auto formats = QImageReader::supportedImageFormats();
615 for (
auto f: formats) {
616 if (QString::fromLatin1(f).toLower() == ext) {
629 if (image.isNull()) {
630 SVCERR <<
"Failed to open image from url \"" << url <<
"\" (local filename \"" <<
getLocalFilename(url) <<
"\"" << endl;
637 Event point = Event(frame).withURI(url);
639 new ChangeEventsCommand(
m_model.untyped,
"Add Image");
650 auto model = ModelById::getAs<ImageModel>(
m_model);
654 if (points.empty())
return;
671 auto model = ModelById::getAs<ImageModel>(
m_model);
677 if (frame < 0) frame = 0;
678 frame = (frame / model->getResolution()) * model->getResolution();
694 auto model = ModelById::getAs<ImageModel>(
m_model);
708 auto model = ModelById::getAs<ImageModel>(
m_model);
709 if (!model)
return false;
712 if (points.empty())
return false;
714 QString image = points.begin()->getURI();
715 QString label = points.begin()->getLabel();
721 if (dialog.exec() == QDialog::Accepted) {
726 new ChangeEventsCommand(
m_model.untyped, tr(
"Edit Image"));
727 command->remove(*points.begin());
728 command->add(points.begin()->
739 auto model = ModelById::getAs<ImageModel>(
m_model);
743 new ChangeEventsCommand(
m_model.untyped, tr(
"Drag Selection"));
746 model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
748 for (Event p: points) {
750 Event moved = p.withFrame(p.getFrame() +
751 newStartFrame - s.getStartFrame());
761 auto model = ModelById::getAs<ImageModel>(
m_model);
765 new ChangeEventsCommand(
m_model.untyped, tr(
"Resize Selection"));
768 model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
770 double ratio = double(newSize.getDuration()) /
double(s.getDuration());
771 double oldStart = double(s.getStartFrame());
772 double newStart = double(newSize.getStartFrame());
774 for (Event p: points) {
776 double newFrame = (double(p.getFrame()) - oldStart) * ratio + newStart;
779 .withFrame(lrint(newFrame));
781 command->add(newPoint);
790 auto model = ModelById::getAs<ImageModel>(
m_model);
794 new ChangeEventsCommand(
m_model.untyped, tr(
"Delete Selection"));
797 model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
799 for (Event p: points) {
809 auto model = ModelById::getAs<ImageModel>(
m_model);
813 model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
815 for (Event p: points) {
824 auto model = ModelById::getAs<ImageModel>(
m_model);
825 if (!model)
return false;
827 const EventVector &points = from.getPoints();
829 bool realign =
false;
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,
839 if (button == QMessageBox::Cancel) {
843 if (button == QMessageBox::Yes) {
848 auto command =
new ChangeEventsCommand(
m_model.untyped, tr(
"Paste"));
850 for (EventVector::const_iterator i = points.begin();
851 i != points.end(); ++i) {
853 sv_frame_t frame = 0;
857 frame = i->getFrame();
861 if (i->hasReferenceFrame()) {
862 frame = i->getReferenceFrame();
865 frame = i->getFrame();
869 Event p = i->withFrame(frame);
877 newPoint = newPoint.withLabel(QString(
"%1").arg(p.getValue()));
879 newPoint = newPoint.withLabel(tr(
"New Point"));
883 command->add(newPoint);
920 SVDEBUG <<
"ImageLayer::checkAddSource(" << img <<
"): yes, trying..." << endl;
929 FileSource *rf =
new FileSource(img, &dialog);
931 SVDEBUG <<
"ok, adding it (local filename = " << rf->getLocalFilename() <<
")" << endl;
941 auto model = ModelById::getAs<ImageModel>(
m_model);
942 const EventVector &points(model->getAllEvents());
944 for (EventVector::const_iterator i = points.begin();
945 i != points.end(); ++i) {
956 FileSource *rf =
dynamic_cast<FileSource *
>(sender());
959 bool shouldEmit =
false;
965 for (FileSourceMap::const_iterator i =
m_fileSources.begin();
967 if (i->second == rf) {
973 if (img ==
"")
return;
976 for (ViewImageMap::iterator i =
m_scaled.begin(); i !=
m_scaled.end(); ++i) {
977 i->second.erase(img);
989 QString indent, QString extraAttributes)
const virtual bool snapToFeatureFrame(LayerGeometryProvider *, sv_frame_t &, int &resolution, SnapType, int) const
Adjust the given frame to snap to the nearest feature, if possible.
void setProperties(const QXmlAttributes &attributes) override
Set the particular properties of a layer (those specific to the subclass) from a set of XML attribute...
void setModel(ModelId model)
virtual QColor getForeground() const =0
void finish(ChangeEventsCommand *command)
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...
virtual QColor getBackground() const =0
void toXml(QTextStream &stream, QString indent="", QString extraAttributes="") const override
Convert the layer's data (though not those of the model it refers to) into XML for file output...
static bool isImageFileSupported(QString url)
void drawEnd(LayerGeometryProvider *v, QMouseEvent *) override
int getPropertyRangeAndValue(const PropertyName &, int *min, int *max, int *deflt) const override
EventVector getLocalPoints(LayerGeometryProvider *v, int x, int y) const
bool snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame, int &resolution, SnapType snap, int ycoord) const override
!! too much overlap with TimeValueLayer/TimeInstantLayer/TextLayer
void resizeSelection(Selection s, Selection newSize) override
static FileSourceMap m_fileSources
bool editOpen(LayerGeometryProvider *, QMouseEvent *) override
Open an editor on the item under the mouse (e.g.
void toXml(QTextStream &stream, QString indent="", QString extraAttributes="") const override
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
std::map< QString, QImage > ImageMap
!! how to reap no-longer-used images?
std::map< QString, FileSource * > FileSourceMap
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.
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.
static QMutex m_staticMutex
static void checkAddSource(QString img, bool synchronise)
void editEnd(LayerGeometryProvider *v, QMouseEvent *) override
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...
void moveSelection(Selection s, sv_frame_t newStartFrame) override
virtual sv_frame_t alignFromReference(LayerGeometryProvider *v, sv_frame_t frame) const
PropertyType getPropertyType(const PropertyName &) const override
QString getPropertyValueLabel(const PropertyName &, int value) const override
QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const override
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.
bool clipboardHasDifferentAlignment(LayerGeometryProvider *v, const Clipboard &clip) const
void deleteSelection(Selection s) override
virtual sv_frame_t alignToReference(LayerGeometryProvider *v, sv_frame_t frame) const
PropertyList getProperties() const override
void connectSignals(ModelId)
virtual int getPaintHeight() const
void drawDrag(LayerGeometryProvider *v, QMouseEvent *) override
void drawStart(LayerGeometryProvider *v, QMouseEvent *) override
void drawImage(LayerGeometryProvider *v, QPainter &paint, const Event &p, int x, int nx) const
void setProperty(const PropertyName &, int value) override
ModelId getModel() const override
Return the ID of the model represented in this layer.
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...
void copy(LayerGeometryProvider *v, Selection s, Clipboard &to) override
int getCompletion(LayerGeometryProvider *) const override
Return the proportion of background work complete in drawing this view, as a percentage – in most ca...
ChangeEventsCommand * m_editingCommand
bool getImageOriginalSize(QString name, QSize &size) const
!! how to reap no-longer-used images?
void editDrag(LayerGeometryProvider *v, QMouseEvent *) override
QString getPropertyLabel(const PropertyName &) const override
static QString getLocalFilename(QString img)
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
void checkAddSourceAndConnect(QString img)
void editStart(LayerGeometryProvider *v, QMouseEvent *) override
virtual View * getView()=0
virtual bool addImage(sv_frame_t frame, QString url)