mirror of
https://github.com/subsurface/subsurface.git
synced 2025-02-19 22:16:15 +00:00
Dive video: paint duration-bar above thumbnail in profile plot
Paint a rectangle on top of thumbnails indicating the run-time of the video. Use the z=100.0-101.0 range for painting the thumbnails, whereby the z-value increases uniformly from first to last thumbnail (sorted by timestamp). The duration-bars are placed at z-values midway between those of the thumbnails. Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
This commit is contained in:
parent
c742885984
commit
b3feaa80e2
9 changed files with 163 additions and 76 deletions
|
@ -93,40 +93,36 @@ Thumbnailer::Thumbnail Thumbnailer::fetchImage(const QString &filename, const QS
|
||||||
mediatype_t type = get_metadata(qPrintable(filename), &md);
|
mediatype_t type = get_metadata(qPrintable(filename), &md);
|
||||||
|
|
||||||
// For io error or video, return early with the appropriate dummy-icon.
|
// For io error or video, return early with the appropriate dummy-icon.
|
||||||
if (type == MEDIATYPE_IO_ERROR) {
|
if (type == MEDIATYPE_IO_ERROR)
|
||||||
return { failImage, MEDIATYPE_IO_ERROR };
|
return { failImage, MEDIATYPE_IO_ERROR, 0 };
|
||||||
} else if (type == MEDIATYPE_VIDEO) {
|
else if (type == MEDIATYPE_VIDEO)
|
||||||
addVideoThumbnailToCache(originalFilename, md.duration);
|
return addVideoThumbnailToCache(originalFilename, md.duration);
|
||||||
return { videoImage, MEDIATYPE_VIDEO };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try if Qt can parse this image. If it does, use this as a thumbnail.
|
// Try if Qt can parse this image. If it does, use this as a thumbnail.
|
||||||
QImage thumb(filename);
|
QImage thumb(filename);
|
||||||
if (!thumb.isNull()) {
|
if (!thumb.isNull()) {
|
||||||
addPictureThumbnailToCache(originalFilename, thumb);
|
int size = maxThumbnailSize();
|
||||||
return { thumb, MEDIATYPE_PICTURE };
|
thumb = thumb.scaled(size, size, Qt::KeepAspectRatio);
|
||||||
|
return addPictureThumbnailToCache(originalFilename, thumb);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Neither our code, nor Qt could determine the type of this object from looking at the data.
|
// Neither our code, nor Qt could determine the type of this object from looking at the data.
|
||||||
// Try to check for a video-file extension. Since we couldn't parse the video file,
|
// Try to check for a video-file extension. Since we couldn't parse the video file,
|
||||||
// we pass 0 as the duration.
|
// we pass 0 as the duration.
|
||||||
if (hasVideoFileExtension(filename)) {
|
if (hasVideoFileExtension(filename))
|
||||||
addVideoThumbnailToCache(originalFilename, {0} );
|
return addVideoThumbnailToCache(originalFilename, {0} );
|
||||||
return { videoImage, MEDIATYPE_VIDEO };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Give up: we simply couldn't determine what this thing is.
|
// Give up: we simply couldn't determine what this thing is.
|
||||||
// But since we managed to read this file, mark this file in the cache as unknown.
|
// But since we managed to read this file, mark this file in the cache as unknown.
|
||||||
addUnknownThumbnailToCache(originalFilename);
|
return addUnknownThumbnailToCache(originalFilename);
|
||||||
return { unknownImage, MEDIATYPE_UNKNOWN };
|
|
||||||
} else if (tryDownload) {
|
} else if (tryDownload) {
|
||||||
// This has to be done in UI main thread, because QNetworkManager refuses
|
// This has to be done in UI main thread, because QNetworkManager refuses
|
||||||
// to treat requests from other threads. invokeMethod() is Qt's way of calling a
|
// to treat requests from other threads. invokeMethod() is Qt's way of calling a
|
||||||
// function in a different thread, namely the thread the called object is associated to.
|
// function in a different thread, namely the thread the called object is associated to.
|
||||||
QMetaObject::invokeMethod(ImageDownloader::instance(), "load", Qt::AutoConnection, Q_ARG(QUrl, url), Q_ARG(QString, originalFilename));
|
QMetaObject::invokeMethod(ImageDownloader::instance(), "load", Qt::AutoConnection, Q_ARG(QUrl, url), Q_ARG(QString, originalFilename));
|
||||||
return { QImage(), MEDIATYPE_STILL_LOADING };
|
return { QImage(), MEDIATYPE_STILL_LOADING, 0 };
|
||||||
}
|
}
|
||||||
return { QImage(), MEDIATYPE_IO_ERROR };
|
return { QImage(), MEDIATYPE_IO_ERROR, 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch a picture based on its original filename. If there is a translated filename (obtained either
|
// Fetch a picture based on its original filename. If there is a translated filename (obtained either
|
||||||
|
@ -142,7 +138,7 @@ Thumbnailer::Thumbnail Thumbnailer::getHashedImage(const QString &filename, bool
|
||||||
// If there is a translated filename, try that first.
|
// If there is a translated filename, try that first.
|
||||||
// Note that we set the default type to io-error, so that if we didn't try
|
// Note that we set the default type to io-error, so that if we didn't try
|
||||||
// the local filename first, we will load the file from the canonical filename.
|
// the local filename first, we will load the file from the canonical filename.
|
||||||
Thumbnail thumbnail { QImage(), MEDIATYPE_IO_ERROR };
|
Thumbnail thumbnail { QImage(), MEDIATYPE_IO_ERROR, 0 };
|
||||||
if (localFilename != filename)
|
if (localFilename != filename)
|
||||||
thumbnail = fetchImage(localFilename, filename, tryDownload);
|
thumbnail = fetchImage(localFilename, filename, tryDownload);
|
||||||
|
|
||||||
|
@ -199,7 +195,9 @@ Thumbnailer::Thumbnail Thumbnailer::getVideoThumbnailFromStream(QDataStream &str
|
||||||
|
|
||||||
// If reading did not succeed, schedule for recalculation - this thumbnail might
|
// If reading did not succeed, schedule for recalculation - this thumbnail might
|
||||||
// have been written by an older version, which couldn't extract the duration.
|
// have been written by an older version, which couldn't extract the duration.
|
||||||
if (stream.status() != QDataStream::Ok)
|
// Likewise test the duration and number of pictures for sanity (no videos longer than 10 h,
|
||||||
|
// no more than 10000 pictures).
|
||||||
|
if (stream.status() != QDataStream::Ok || duration > 36000 || numPics > 10000)
|
||||||
return { QImage(), MEDIATYPE_VIDEO, 0 };
|
return { QImage(), MEDIATYPE_VIDEO, 0 };
|
||||||
|
|
||||||
// Currently, we support only one picture
|
// Currently, we support only one picture
|
||||||
|
@ -211,7 +209,7 @@ Thumbnailer::Thumbnail Thumbnailer::getVideoThumbnailFromStream(QDataStream &str
|
||||||
}
|
}
|
||||||
|
|
||||||
// No picture -> show dummy-icon
|
// No picture -> show dummy-icon
|
||||||
return { res.isNull() ? videoImage : res, MEDIATYPE_VIDEO, {(int32_t)duration} };
|
return { res.isNull() ? videoImage : res, MEDIATYPE_VIDEO, (int32_t)duration };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch a thumbnail from cache.
|
// Fetch a thumbnail from cache.
|
||||||
|
@ -220,7 +218,7 @@ Thumbnailer::Thumbnail Thumbnailer::getThumbnailFromCache(const QString &picture
|
||||||
{
|
{
|
||||||
QString filename = thumbnailFileName(picture_filename);
|
QString filename = thumbnailFileName(picture_filename);
|
||||||
if (filename.isEmpty())
|
if (filename.isEmpty())
|
||||||
return { QImage(), MEDIATYPE_UNKNOWN };
|
return { QImage(), MEDIATYPE_UNKNOWN, 0 };
|
||||||
QFile file(filename);
|
QFile file(filename);
|
||||||
|
|
||||||
if (prefs.auto_recalculate_thumbnails) {
|
if (prefs.auto_recalculate_thumbnails) {
|
||||||
|
@ -256,7 +254,7 @@ Thumbnailer::Thumbnail Thumbnailer::getThumbnailFromCache(const QString &picture
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Thumbnailer::addVideoThumbnailToCache(const QString &picture_filename, duration_t duration)
|
Thumbnailer::Thumbnail Thumbnailer::addVideoThumbnailToCache(const QString &picture_filename, duration_t duration)
|
||||||
{
|
{
|
||||||
// The format of video thumbnails:
|
// The format of video thumbnails:
|
||||||
// uint32 MEDIATYPE_VIDEO
|
// uint32 MEDIATYPE_VIDEO
|
||||||
|
@ -267,8 +265,7 @@ void Thumbnailer::addVideoThumbnailToCache(const QString &picture_filename, dura
|
||||||
// QImage frame
|
// QImage frame
|
||||||
QString filename = thumbnailFileName(picture_filename);
|
QString filename = thumbnailFileName(picture_filename);
|
||||||
QSaveFile file(filename);
|
QSaveFile file(filename);
|
||||||
if (!file.open(QIODevice::WriteOnly))
|
if (file.open(QIODevice::WriteOnly)) {
|
||||||
return;
|
|
||||||
QDataStream stream(&file);
|
QDataStream stream(&file);
|
||||||
|
|
||||||
stream << (quint32)MEDIATYPE_VIDEO;
|
stream << (quint32)MEDIATYPE_VIDEO;
|
||||||
|
@ -276,35 +273,36 @@ void Thumbnailer::addVideoThumbnailToCache(const QString &picture_filename, dura
|
||||||
stream << (quint32)0; // Currently, we don't support extraction of images
|
stream << (quint32)0; // Currently, we don't support extraction of images
|
||||||
file.commit();
|
file.commit();
|
||||||
}
|
}
|
||||||
|
return { videoImage, MEDIATYPE_VIDEO, duration };
|
||||||
|
}
|
||||||
|
|
||||||
void Thumbnailer::addPictureThumbnailToCache(const QString &picture_filename, const QImage &thumbnail)
|
Thumbnailer::Thumbnail Thumbnailer::addPictureThumbnailToCache(const QString &picture_filename, const QImage &thumbnail)
|
||||||
{
|
{
|
||||||
if (thumbnail.isNull())
|
|
||||||
return;
|
|
||||||
|
|
||||||
// The format of a picture-thumbnail is very simple:
|
// The format of a picture-thumbnail is very simple:
|
||||||
// uint32 MEDIATYPE_PICTURE
|
// uint32 MEDIATYPE_PICTURE
|
||||||
// QImage thumbnail
|
// QImage thumbnail
|
||||||
QString filename = thumbnailFileName(picture_filename);
|
QString filename = thumbnailFileName(picture_filename);
|
||||||
QSaveFile file(filename);
|
QSaveFile file(filename);
|
||||||
if (!file.open(QIODevice::WriteOnly))
|
if (file.open(QIODevice::WriteOnly)) {
|
||||||
return;
|
|
||||||
QDataStream stream(&file);
|
QDataStream stream(&file);
|
||||||
|
|
||||||
stream << (quint32)MEDIATYPE_PICTURE;
|
stream << (quint32)MEDIATYPE_PICTURE;
|
||||||
stream << thumbnail;
|
stream << thumbnail;
|
||||||
file.commit();
|
file.commit();
|
||||||
}
|
}
|
||||||
|
return { thumbnail, MEDIATYPE_PICTURE, 0 };
|
||||||
|
}
|
||||||
|
|
||||||
void Thumbnailer::addUnknownThumbnailToCache(const QString &picture_filename)
|
Thumbnailer::Thumbnail Thumbnailer::addUnknownThumbnailToCache(const QString &picture_filename)
|
||||||
{
|
{
|
||||||
QString filename = thumbnailFileName(picture_filename);
|
QString filename = thumbnailFileName(picture_filename);
|
||||||
QSaveFile file(filename);
|
QSaveFile file(filename);
|
||||||
if (!file.open(QIODevice::WriteOnly))
|
if (file.open(QIODevice::WriteOnly)) {
|
||||||
return;
|
|
||||||
QDataStream stream(&file);
|
QDataStream stream(&file);
|
||||||
stream << (quint32)MEDIATYPE_UNKNOWN;
|
stream << (quint32)MEDIATYPE_UNKNOWN;
|
||||||
}
|
}
|
||||||
|
return { unknownImage, MEDIATYPE_UNKNOWN, 0 };
|
||||||
|
}
|
||||||
|
|
||||||
void Thumbnailer::recalculate(QString filename)
|
void Thumbnailer::recalculate(QString filename)
|
||||||
{
|
{
|
||||||
|
@ -317,7 +315,7 @@ void Thumbnailer::recalculate(QString filename)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
QMutexLocker l(&lock);
|
QMutexLocker l(&lock);
|
||||||
emit thumbnailChanged(filename, thumbnail.img);
|
emit thumbnailChanged(filename, thumbnail.img, thumbnail.duration);
|
||||||
workingOn.remove(filename);
|
workingOn.remove(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -339,7 +337,7 @@ void Thumbnailer::processItem(QString filename, bool tryDownload)
|
||||||
}
|
}
|
||||||
|
|
||||||
QMutexLocker l(&lock);
|
QMutexLocker l(&lock);
|
||||||
emit thumbnailChanged(filename, thumbnail.img);
|
emit thumbnailChanged(filename, thumbnail.img, thumbnail.duration);
|
||||||
workingOn.remove(filename);
|
workingOn.remove(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -352,7 +350,7 @@ void Thumbnailer::imageDownloaded(QString filename)
|
||||||
|
|
||||||
void Thumbnailer::imageDownloadFailed(QString filename)
|
void Thumbnailer::imageDownloadFailed(QString filename)
|
||||||
{
|
{
|
||||||
emit thumbnailChanged(filename, failImage);
|
emit thumbnailChanged(filename, failImage, duration_t{ 0 });
|
||||||
QMutexLocker l(&lock);
|
QMutexLocker l(&lock);
|
||||||
workingOn.remove(filename);
|
workingOn.remove(filename);
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ public slots:
|
||||||
void imageDownloaded(QString filename);
|
void imageDownloaded(QString filename);
|
||||||
void imageDownloadFailed(QString filename);
|
void imageDownloadFailed(QString filename);
|
||||||
signals:
|
signals:
|
||||||
void thumbnailChanged(QString filename, QImage thumbnail);
|
void thumbnailChanged(QString filename, QImage thumbnail, duration_t duration);
|
||||||
private:
|
private:
|
||||||
struct Thumbnail {
|
struct Thumbnail {
|
||||||
QImage img;
|
QImage img;
|
||||||
|
@ -56,9 +56,9 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
Thumbnailer();
|
Thumbnailer();
|
||||||
static void addPictureThumbnailToCache(const QString &picture_filename, const QImage &thumbnail);
|
Thumbnail addPictureThumbnailToCache(const QString &picture_filename, const QImage &thumbnail);
|
||||||
static void addVideoThumbnailToCache(const QString &picture_filename, duration_t duration);
|
Thumbnail addVideoThumbnailToCache(const QString &picture_filename, duration_t duration);
|
||||||
static void addUnknownThumbnailToCache(const QString &picture_filename);
|
Thumbnail addUnknownThumbnailToCache(const QString &picture_filename);
|
||||||
void recalculate(QString filename);
|
void recalculate(QString filename);
|
||||||
void processItem(QString filename, bool tryDownload);
|
void processItem(QString filename, bool tryDownload);
|
||||||
Thumbnail getThumbnailFromCache(const QString &picture_filename);
|
Thumbnail getThumbnailFromCache(const QString &picture_filename);
|
||||||
|
|
|
@ -42,7 +42,8 @@ void CloseButtonItem::show()
|
||||||
DivePictureItem::DivePictureItem(QGraphicsItem *parent): DivePixmapItem(parent),
|
DivePictureItem::DivePictureItem(QGraphicsItem *parent): DivePixmapItem(parent),
|
||||||
canvas(new QGraphicsRectItem(this)),
|
canvas(new QGraphicsRectItem(this)),
|
||||||
shadow(new QGraphicsRectItem(this)),
|
shadow(new QGraphicsRectItem(this)),
|
||||||
button(new CloseButtonItem(this))
|
button(new CloseButtonItem(this)),
|
||||||
|
baseZValue(0.0)
|
||||||
{
|
{
|
||||||
setFlag(ItemIgnoresTransformations);
|
setFlag(ItemIgnoresTransformations);
|
||||||
setAcceptHoverEvents(true);
|
setAcceptHoverEvents(true);
|
||||||
|
@ -67,6 +68,14 @@ DivePictureItem::DivePictureItem(QGraphicsItem *parent): DivePixmapItem(parent),
|
||||||
button->hide();
|
button->hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The base z-value is used for correct paint-order of the thumbnails. On hoverEnter the z-value is raised
|
||||||
|
// so that the thumbnail is drawn on top of all other thumbnails and on hoverExit it is restored to the base value.
|
||||||
|
void DivePictureItem::setBaseZValue(double z)
|
||||||
|
{
|
||||||
|
baseZValue = z;
|
||||||
|
setZValue(z);
|
||||||
|
}
|
||||||
|
|
||||||
void DivePictureItem::settingsChanged()
|
void DivePictureItem::settingsChanged()
|
||||||
{
|
{
|
||||||
setVisible(prefs.show_pictures_in_profile);
|
setVisible(prefs.show_pictures_in_profile);
|
||||||
|
@ -85,7 +94,7 @@ void DivePictureItem::setPixmap(const QPixmap &pix)
|
||||||
void DivePictureItem::hoverEnterEvent(QGraphicsSceneHoverEvent*)
|
void DivePictureItem::hoverEnterEvent(QGraphicsSceneHoverEvent*)
|
||||||
{
|
{
|
||||||
Animations::scaleTo(this, 1.0);
|
Animations::scaleTo(this, 1.0);
|
||||||
setZValue(5);
|
setZValue(baseZValue + 5.0);
|
||||||
|
|
||||||
button->setOpacity(0);
|
button->setOpacity(0);
|
||||||
button->show();
|
button->show();
|
||||||
|
@ -100,7 +109,7 @@ void DivePictureItem::setFileUrl(const QString &s)
|
||||||
void DivePictureItem::hoverLeaveEvent(QGraphicsSceneHoverEvent*)
|
void DivePictureItem::hoverLeaveEvent(QGraphicsSceneHoverEvent*)
|
||||||
{
|
{
|
||||||
Animations::scaleTo(this, 0.2);
|
Animations::scaleTo(this, 0.2);
|
||||||
setZValue(0);
|
setZValue(baseZValue);
|
||||||
Animations::hide(button);
|
Animations::hide(button);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@ class DivePictureItem : public DivePixmapItem {
|
||||||
public:
|
public:
|
||||||
DivePictureItem(QGraphicsItem *parent = 0);
|
DivePictureItem(QGraphicsItem *parent = 0);
|
||||||
void setPixmap(const QPixmap& pix);
|
void setPixmap(const QPixmap& pix);
|
||||||
|
void setBaseZValue(double z);
|
||||||
public slots:
|
public slots:
|
||||||
void settingsChanged();
|
void settingsChanged();
|
||||||
void removePicture();
|
void removePicture();
|
||||||
|
@ -45,6 +46,7 @@ private:
|
||||||
QGraphicsRectItem *canvas;
|
QGraphicsRectItem *canvas;
|
||||||
QGraphicsRectItem *shadow;
|
QGraphicsRectItem *shadow;
|
||||||
CloseButtonItem *button;
|
CloseButtonItem *button;
|
||||||
|
double baseZValue;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // DIVEPIXMAPITEM_H
|
#endif // DIVEPIXMAPITEM_H
|
||||||
|
|
|
@ -87,6 +87,10 @@ static struct _ItemPos {
|
||||||
_Axis heartBeatWithTankBar;
|
_Axis heartBeatWithTankBar;
|
||||||
} itemPos;
|
} itemPos;
|
||||||
|
|
||||||
|
// Constant describing at which z-level the thumbnails are located.
|
||||||
|
// We might add more constants here for easier customability.
|
||||||
|
static const double thumbnailBaseZValue = 100.0;
|
||||||
|
|
||||||
ProfileWidget2::ProfileWidget2(QWidget *parent) : QGraphicsView(parent),
|
ProfileWidget2::ProfileWidget2(QWidget *parent) : QGraphicsView(parent),
|
||||||
currentState(INVALID),
|
currentState(INVALID),
|
||||||
dataModel(new DivePlotDataModel(this)),
|
dataModel(new DivePlotDataModel(this)),
|
||||||
|
@ -975,6 +979,21 @@ void ProfileWidget2::fixBackgroundPos()
|
||||||
background->setY(mapToScene(y, 20).y());
|
background->setY(mapToScene(y, 20).y());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ProfileWidget2::scale(qreal sx, qreal sy)
|
||||||
|
{
|
||||||
|
QGraphicsView::scale(sx, sy);
|
||||||
|
|
||||||
|
#ifndef SUBSURFACE_MOBILE
|
||||||
|
// Since the zoom level changed, adjust the duration bars accordingly.
|
||||||
|
// We want to grow/shrink the length, but not the height and pen.
|
||||||
|
for (PictureEntry &p: pictures)
|
||||||
|
updateDurationLine(p);
|
||||||
|
|
||||||
|
// Since we created new duration lines, we have to update the order in which the thumbnails is painted.
|
||||||
|
updateThumbnailPaintOrder();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
#ifndef SUBSURFACE_MOBILE
|
#ifndef SUBSURFACE_MOBILE
|
||||||
void ProfileWidget2::wheelEvent(QWheelEvent *event)
|
void ProfileWidget2::wheelEvent(QWheelEvent *event)
|
||||||
{
|
{
|
||||||
|
@ -2064,9 +2083,37 @@ void ProfileWidget2::clearPictures()
|
||||||
pictures.clear();
|
pictures.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const double unscaledDurationLineWidth = 2.5;
|
||||||
|
static const double unscaledDurationLinePenWidth = 0.5;
|
||||||
|
|
||||||
|
// Reset the duration line after an image was moved or we found a new duration
|
||||||
|
void ProfileWidget2::updateDurationLine(PictureEntry &e)
|
||||||
|
{
|
||||||
|
if (e.duration.seconds > 0) {
|
||||||
|
// We know the duration of this video, reset the line symbolizing its extent accordingly
|
||||||
|
double begin = timeAxis->posAtValue(e.offset.seconds);
|
||||||
|
double end = timeAxis->posAtValue(e.offset.seconds + e.duration.seconds);
|
||||||
|
double y = e.thumbnail->y();
|
||||||
|
|
||||||
|
// Undo scaling for pen-width and line-width. For this purpose, we use the scaling of the y-axis.
|
||||||
|
double scale = transform().m22();
|
||||||
|
double durationLineWidth = unscaledDurationLineWidth / scale;
|
||||||
|
double durationLinePenWidth = unscaledDurationLinePenWidth / scale;
|
||||||
|
e.durationLine.reset(new QGraphicsRectItem(begin, y - durationLineWidth - durationLinePenWidth, end - begin, durationLineWidth));
|
||||||
|
e.durationLine->setPen(QPen(getColor(GF_LINE, isGrayscale), durationLinePenWidth));
|
||||||
|
e.durationLine->setBrush(getColor(::BACKGROUND, isGrayscale));
|
||||||
|
e.durationLine->setVisible(prefs.show_pictures_in_profile);
|
||||||
|
scene()->addItem(e.durationLine.get());
|
||||||
|
} else {
|
||||||
|
// This is either a picture or a video with unknown duration.
|
||||||
|
// In case there was a line (how could that be?) remove it.
|
||||||
|
e.durationLine.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// This function is called asynchronously by the thumbnailer if a thumbnail
|
// This function is called asynchronously by the thumbnailer if a thumbnail
|
||||||
// was fetched from disk or freshly calculated.
|
// was fetched from disk or freshly calculated.
|
||||||
void ProfileWidget2::updateThumbnail(QString filename, QImage thumbnail)
|
void ProfileWidget2::updateThumbnail(QString filename, QImage thumbnail, duration_t duration)
|
||||||
{
|
{
|
||||||
// Find the picture with the given filename
|
// Find the picture with the given filename
|
||||||
auto it = std::find_if(pictures.begin(), pictures.end(), [&filename](const PictureEntry &e)
|
auto it = std::find_if(pictures.begin(), pictures.end(), [&filename](const PictureEntry &e)
|
||||||
|
@ -2078,11 +2125,20 @@ void ProfileWidget2::updateThumbnail(QString filename, QImage thumbnail)
|
||||||
// Replace the pixmap of the thumbnail with the newly calculated one.
|
// Replace the pixmap of the thumbnail with the newly calculated one.
|
||||||
int size = Thumbnailer::defaultThumbnailSize();
|
int size = Thumbnailer::defaultThumbnailSize();
|
||||||
it->thumbnail->setPixmap(QPixmap::fromImage(thumbnail.scaled(size, size, Qt::KeepAspectRatio)));
|
it->thumbnail->setPixmap(QPixmap::fromImage(thumbnail.scaled(size, size, Qt::KeepAspectRatio)));
|
||||||
|
|
||||||
|
// If the duration changed, update the line
|
||||||
|
if (duration.seconds != it->duration.seconds) {
|
||||||
|
it->duration = duration;
|
||||||
|
updateDurationLine(*it);
|
||||||
|
// If we created / removed a duration line, we have to update the thumbnail paint order.
|
||||||
|
updateThumbnailPaintOrder();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a PictureEntry object and add its thumbnail to the scene if profile pictures are shown.
|
// Create a PictureEntry object and add its thumbnail to the scene if profile pictures are shown.
|
||||||
ProfileWidget2::PictureEntry::PictureEntry(offset_t offsetIn, const QString &filenameIn, QGraphicsScene *scene) : offset(offsetIn),
|
ProfileWidget2::PictureEntry::PictureEntry(offset_t offsetIn, const QString &filenameIn, QGraphicsScene *scene) : offset(offsetIn),
|
||||||
|
duration(duration_t {0}),
|
||||||
filename(filenameIn),
|
filename(filenameIn),
|
||||||
thumbnail(new DivePictureItem)
|
thumbnail(new DivePictureItem)
|
||||||
{
|
{
|
||||||
|
@ -2101,23 +2157,37 @@ bool ProfileWidget2::PictureEntry::operator< (const PictureEntry &e) const
|
||||||
return std::tie(offset.seconds, filename) < std::tie(e.offset.seconds, e.filename);
|
return std::tie(offset.seconds, filename) < std::tie(e.offset.seconds, e.filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This function updates the paint order of the thumbnails and duration-lines, such that later
|
||||||
|
// thumbnails are painted on top of previous thumbnails and duration-lines on top of the thumbnail
|
||||||
|
// they belong to.
|
||||||
|
void ProfileWidget2::updateThumbnailPaintOrder()
|
||||||
|
{
|
||||||
|
if (!pictures.size())
|
||||||
|
return;
|
||||||
|
// To get the correct sort order, we place in thumbnails at equal z-distances
|
||||||
|
// between thumbnailBaseZValue and (thumbnailBaseZValue + 1.0).
|
||||||
|
// Duration-lines are placed between the thumbnails.
|
||||||
|
double z = thumbnailBaseZValue;
|
||||||
|
double step = 1.0 / (double)pictures.size();
|
||||||
|
for (PictureEntry &e: pictures) {
|
||||||
|
e.thumbnail->setBaseZValue(z);
|
||||||
|
if (e.durationLine)
|
||||||
|
e.durationLine->setZValue(z + step / 2.0);
|
||||||
|
z += step;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate the y-coordinates of the thumbnails, which are supposed to be sorted by x-coordinate.
|
// Calculate the y-coordinates of the thumbnails, which are supposed to be sorted by x-coordinate.
|
||||||
// This will also change the order in which the thumbnails are painted, to avoid weird effects,
|
// This will also change the order in which the thumbnails are painted, to avoid weird effects,
|
||||||
// when items are added later to the scene. This is done using the QGraphicsItem::packBefore() function.
|
// when items are added later to the scene. This is done using the QGraphicsItem::packBefore() function.
|
||||||
// We can't use the z-value, because that will be modified on hoverEnter and hoverExit events.
|
// We can't use the z-value, because that will be modified on hoverEnter and hoverExit events.
|
||||||
void ProfileWidget2::calculatePictureYPositions()
|
void ProfileWidget2::calculatePictureYPositions()
|
||||||
{
|
{
|
||||||
// Quit early if there are no items. The last loop in this function assumes that the vector is not empty.
|
|
||||||
if (pictures.empty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
double lastX = -1.0, lastY = 0.0;
|
double lastX = -1.0, lastY = 0.0;
|
||||||
for (auto it = pictures.begin(); it != pictures.end(); ++it) {
|
for (PictureEntry &e: pictures) {
|
||||||
if (!it->thumbnail)
|
|
||||||
continue;
|
|
||||||
// let's put the picture at the correct time, but at a fixed "depth" on the profile
|
// let's put the picture at the correct time, but at a fixed "depth" on the profile
|
||||||
// not sure this is ideal, but it seems to look right.
|
// not sure this is ideal, but it seems to look right.
|
||||||
double x = it->thumbnail->x();
|
double x = e.thumbnail->x();
|
||||||
double y;
|
double y;
|
||||||
if (lastX >= 0.0 && fabs(x - lastX) < 3 && lastY <= (10 + 14 * 3))
|
if (lastX >= 0.0 && fabs(x - lastX) < 3 && lastY <= (10 + 14 * 3))
|
||||||
y = lastY + 3;
|
y = lastY + 3;
|
||||||
|
@ -2125,18 +2195,10 @@ void ProfileWidget2::calculatePictureYPositions()
|
||||||
y = 10;
|
y = 10;
|
||||||
lastX = x;
|
lastX = x;
|
||||||
lastY = y;
|
lastY = y;
|
||||||
it->thumbnail->setY(y);
|
e.thumbnail->setY(y);
|
||||||
|
updateDurationLine(e); // If we changed the y-position, we also have to change the duration-line.
|
||||||
// hoverEnter and hoverExit events modify the z-value. Objects with different z-values
|
|
||||||
// are not considered in stackBefore() calls. Therefore, just to be sure, reset the
|
|
||||||
// z-values of all picture entries.
|
|
||||||
it->thumbnail->setZValue(0.0);
|
|
||||||
}
|
}
|
||||||
|
updateThumbnailPaintOrder();
|
||||||
// Plot the items in the correct order. Experience showed that this works only
|
|
||||||
// if we rearrange the items starting from the back. Therefore, use rbegin() and rend().
|
|
||||||
for (auto it = pictures.rbegin(); std::next(it) != pictures.rend(); ++it)
|
|
||||||
std::next(it)->thumbnail->stackBefore(it->thumbnail.get());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProfileWidget2::updateThumbnailXPos(PictureEntry &e)
|
void ProfileWidget2::updateThumbnailXPos(PictureEntry &e)
|
||||||
|
|
|
@ -74,6 +74,7 @@ public:
|
||||||
|
|
||||||
ProfileWidget2(QWidget *parent = 0);
|
ProfileWidget2(QWidget *parent = 0);
|
||||||
void resetZoom();
|
void resetZoom();
|
||||||
|
void scale(qreal sx, qreal sy);
|
||||||
void plotDive(struct dive *d = 0, bool force = false, bool clearPictures = false);
|
void plotDive(struct dive *d = 0, bool force = false, bool clearPictures = false);
|
||||||
void setupItem(AbstractProfilePolygonItem *item, DiveCartesianAxis *vAxis, int vData, int hData, int zValue);
|
void setupItem(AbstractProfilePolygonItem *item, DiveCartesianAxis *vAxis, int vData, int hData, int zValue);
|
||||||
void setPrintMode(bool mode, bool grayscale = false);
|
void setPrintMode(bool mode, bool grayscale = false);
|
||||||
|
@ -127,7 +128,7 @@ slots: // Necessary to call from QAction's signals.
|
||||||
void deleteCurrentDC();
|
void deleteCurrentDC();
|
||||||
void pointInserted(const QModelIndex &parent, int start, int end);
|
void pointInserted(const QModelIndex &parent, int start, int end);
|
||||||
void pointsRemoved(const QModelIndex &, int start, int end);
|
void pointsRemoved(const QModelIndex &, int start, int end);
|
||||||
void updateThumbnail(QString filename, QImage thumbnail);
|
void updateThumbnail(QString filename, QImage thumbnail, duration_t duration);
|
||||||
|
|
||||||
/* this is called for every move on the handlers. maybe we can speed up this a bit? */
|
/* this is called for every move on the handlers. maybe we can speed up this a bit? */
|
||||||
void recreatePlannedDive();
|
void recreatePlannedDive();
|
||||||
|
@ -234,14 +235,19 @@ private:
|
||||||
// Pictures that are outside of the dive time are not shown.
|
// Pictures that are outside of the dive time are not shown.
|
||||||
struct PictureEntry {
|
struct PictureEntry {
|
||||||
offset_t offset;
|
offset_t offset;
|
||||||
|
duration_t duration;
|
||||||
QString filename;
|
QString filename;
|
||||||
std::unique_ptr<DivePictureItem> thumbnail;
|
std::unique_ptr<DivePictureItem> thumbnail;
|
||||||
|
// For videos with known duration, we represent the duration of the video by a line
|
||||||
|
std::unique_ptr<QGraphicsRectItem> durationLine;
|
||||||
PictureEntry (offset_t offsetIn, const QString &filenameIn, QGraphicsScene *scene);
|
PictureEntry (offset_t offsetIn, const QString &filenameIn, QGraphicsScene *scene);
|
||||||
bool operator< (const PictureEntry &e) const;
|
bool operator< (const PictureEntry &e) const;
|
||||||
};
|
};
|
||||||
void updateThumbnailXPos(PictureEntry &e);
|
void updateThumbnailXPos(PictureEntry &e);
|
||||||
std::vector<PictureEntry> pictures;
|
std::vector<PictureEntry> pictures;
|
||||||
void calculatePictureYPositions();
|
void calculatePictureYPositions();
|
||||||
|
void updateDurationLine(PictureEntry &e);
|
||||||
|
void updateThumbnailPaintOrder();
|
||||||
|
|
||||||
QList<DiveHandler *> handles;
|
QList<DiveHandler *> handles;
|
||||||
void repositionDiveHandlers();
|
void repositionDiveHandlers();
|
||||||
|
|
|
@ -165,7 +165,7 @@ int DivePictureModel::findPictureId(const QString &filename)
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DivePictureModel::updateThumbnail(QString filename, QImage thumbnail)
|
void DivePictureModel::updateThumbnail(QString filename, QImage thumbnail, duration_t)
|
||||||
{
|
{
|
||||||
int i = findPictureId(filename);
|
int i = findPictureId(filename);
|
||||||
if (i >= 0) {
|
if (i >= 0) {
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
#ifndef DIVEPICTUREMODEL_H
|
#ifndef DIVEPICTUREMODEL_H
|
||||||
#define DIVEPICTUREMODEL_H
|
#define DIVEPICTUREMODEL_H
|
||||||
|
|
||||||
|
#include "core/units.h"
|
||||||
|
|
||||||
#include <QAbstractTableModel>
|
#include <QAbstractTableModel>
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QFuture>
|
#include <QFuture>
|
||||||
|
@ -28,7 +30,7 @@ signals:
|
||||||
void picturesRemoved(const QVector<QString> &fileUrls);
|
void picturesRemoved(const QVector<QString> &fileUrls);
|
||||||
public slots:
|
public slots:
|
||||||
void setZoomLevel(int level);
|
void setZoomLevel(int level);
|
||||||
void updateThumbnail(QString filename, QImage thumbnail);
|
void updateThumbnail(QString filename, QImage thumbnail, duration_t duration);
|
||||||
private:
|
private:
|
||||||
DivePictureModel();
|
DivePictureModel();
|
||||||
QVector<PictureEntry> pictures;
|
QVector<PictureEntry> pictures;
|
||||||
|
|
|
@ -31,10 +31,12 @@
|
||||||
#ifndef SUBSURFACE_TEST_DATA
|
#ifndef SUBSURFACE_TEST_DATA
|
||||||
QObject *qqWindowObject = NULL;
|
QObject *qqWindowObject = NULL;
|
||||||
|
|
||||||
|
static void register_meta_types();
|
||||||
void init_ui()
|
void init_ui()
|
||||||
{
|
{
|
||||||
init_qt_late();
|
init_qt_late();
|
||||||
register_qml_types();
|
register_qml_types();
|
||||||
|
register_meta_types();
|
||||||
#ifndef SUBSURFACE_MOBILE
|
#ifndef SUBSURFACE_MOBILE
|
||||||
PluginManager::instance().loadPlugins();
|
PluginManager::instance().loadPlugins();
|
||||||
|
|
||||||
|
@ -137,6 +139,12 @@ void run_ui()
|
||||||
#endif // SUBSURFACE_MOBILE
|
#endif // SUBSURFACE_MOBILE
|
||||||
qApp->exec();
|
qApp->exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Q_DECLARE_METATYPE(duration_t)
|
||||||
|
static void register_meta_types()
|
||||||
|
{
|
||||||
|
qRegisterMetaType<duration_t>();
|
||||||
|
}
|
||||||
#endif // not SUBSURFACE_TEST_DATA
|
#endif // not SUBSURFACE_TEST_DATA
|
||||||
|
|
||||||
void register_qml_types()
|
void register_qml_types()
|
||||||
|
|
Loading…
Add table
Reference in a new issue