aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/imagedownloader.cpp90
-rw-r--r--core/imagedownloader.h8
-rw-r--r--profile-widget/divepixmapitem.cpp15
-rw-r--r--profile-widget/divepixmapitem.h2
-rw-r--r--profile-widget/profilewidget2.cpp102
-rw-r--r--profile-widget/profilewidget2.h8
-rw-r--r--qt-models/divepicturemodel.cpp2
-rw-r--r--qt-models/divepicturemodel.h4
-rw-r--r--subsurface-helper.cpp8
9 files changed, 163 insertions, 76 deletions
diff --git a/core/imagedownloader.cpp b/core/imagedownloader.cpp
index fd155bec6..7dc35695d 100644
--- a/core/imagedownloader.cpp
+++ b/core/imagedownloader.cpp
@@ -93,40 +93,36 @@ Thumbnailer::Thumbnail Thumbnailer::fetchImage(const QString &filename, const QS
mediatype_t type = get_metadata(qPrintable(filename), &md);
// For io error or video, return early with the appropriate dummy-icon.
- if (type == MEDIATYPE_IO_ERROR) {
- return { failImage, MEDIATYPE_IO_ERROR };
- } else if (type == MEDIATYPE_VIDEO) {
- addVideoThumbnailToCache(originalFilename, md.duration);
- return { videoImage, MEDIATYPE_VIDEO };
- }
+ if (type == MEDIATYPE_IO_ERROR)
+ return { failImage, MEDIATYPE_IO_ERROR, 0 };
+ else if (type == MEDIATYPE_VIDEO)
+ return addVideoThumbnailToCache(originalFilename, md.duration);
// Try if Qt can parse this image. If it does, use this as a thumbnail.
QImage thumb(filename);
if (!thumb.isNull()) {
- addPictureThumbnailToCache(originalFilename, thumb);
- return { thumb, MEDIATYPE_PICTURE };
+ int size = maxThumbnailSize();
+ 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.
// Try to check for a video-file extension. Since we couldn't parse the video file,
// we pass 0 as the duration.
- if (hasVideoFileExtension(filename)) {
- addVideoThumbnailToCache(originalFilename, {0} );
- return { videoImage, MEDIATYPE_VIDEO };
- }
+ if (hasVideoFileExtension(filename))
+ return addVideoThumbnailToCache(originalFilename, {0} );
// 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.
- addUnknownThumbnailToCache(originalFilename);
- return { unknownImage, MEDIATYPE_UNKNOWN };
+ return addUnknownThumbnailToCache(originalFilename);
} else if (tryDownload) {
// 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
// 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));
- 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
@@ -142,7 +138,7 @@ Thumbnailer::Thumbnail Thumbnailer::getHashedImage(const QString &filename, bool
// 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
// 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)
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
// 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 };
// Currently, we support only one picture
@@ -211,7 +209,7 @@ Thumbnailer::Thumbnail Thumbnailer::getVideoThumbnailFromStream(QDataStream &str
}
// 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.
@@ -220,7 +218,7 @@ Thumbnailer::Thumbnail Thumbnailer::getThumbnailFromCache(const QString &picture
{
QString filename = thumbnailFileName(picture_filename);
if (filename.isEmpty())
- return { QImage(), MEDIATYPE_UNKNOWN };
+ return { QImage(), MEDIATYPE_UNKNOWN, 0 };
QFile file(filename);
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:
// uint32 MEDIATYPE_VIDEO
@@ -267,43 +265,43 @@ void Thumbnailer::addVideoThumbnailToCache(const QString &picture_filename, dura
// QImage frame
QString filename = thumbnailFileName(picture_filename);
QSaveFile file(filename);
- if (!file.open(QIODevice::WriteOnly))
- return;
- QDataStream stream(&file);
+ if (file.open(QIODevice::WriteOnly)) {
+ QDataStream stream(&file);
- stream << (quint32)MEDIATYPE_VIDEO;
- stream << (quint32)duration.seconds;
- stream << (quint32)0; // Currently, we don't support extraction of images
- file.commit();
+ stream << (quint32)MEDIATYPE_VIDEO;
+ stream << (quint32)duration.seconds;
+ stream << (quint32)0; // Currently, we don't support extraction of images
+ 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:
// uint32 MEDIATYPE_PICTURE
// QImage thumbnail
QString filename = thumbnailFileName(picture_filename);
QSaveFile file(filename);
- if (!file.open(QIODevice::WriteOnly))
- return;
- QDataStream stream(&file);
+ if (file.open(QIODevice::WriteOnly)) {
+ QDataStream stream(&file);
- stream << (quint32)MEDIATYPE_PICTURE;
- stream << thumbnail;
- file.commit();
+ stream << (quint32)MEDIATYPE_PICTURE;
+ stream << thumbnail;
+ 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);
QSaveFile file(filename);
- if (!file.open(QIODevice::WriteOnly))
- return;
- QDataStream stream(&file);
- stream << (quint32)MEDIATYPE_UNKNOWN;
+ if (file.open(QIODevice::WriteOnly)) {
+ QDataStream stream(&file);
+ stream << (quint32)MEDIATYPE_UNKNOWN;
+ }
+ return { unknownImage, MEDIATYPE_UNKNOWN, 0 };
}
void Thumbnailer::recalculate(QString filename)
@@ -317,7 +315,7 @@ void Thumbnailer::recalculate(QString filename)
return;
QMutexLocker l(&lock);
- emit thumbnailChanged(filename, thumbnail.img);
+ emit thumbnailChanged(filename, thumbnail.img, thumbnail.duration);
workingOn.remove(filename);
}
@@ -339,7 +337,7 @@ void Thumbnailer::processItem(QString filename, bool tryDownload)
}
QMutexLocker l(&lock);
- emit thumbnailChanged(filename, thumbnail.img);
+ emit thumbnailChanged(filename, thumbnail.img, thumbnail.duration);
workingOn.remove(filename);
}
@@ -352,7 +350,7 @@ void Thumbnailer::imageDownloaded(QString filename)
void Thumbnailer::imageDownloadFailed(QString filename)
{
- emit thumbnailChanged(filename, failImage);
+ emit thumbnailChanged(filename, failImage, duration_t{ 0 });
QMutexLocker l(&lock);
workingOn.remove(filename);
}
diff --git a/core/imagedownloader.h b/core/imagedownloader.h
index dcdf08d20..cab945e5f 100644
--- a/core/imagedownloader.h
+++ b/core/imagedownloader.h
@@ -47,7 +47,7 @@ public slots:
void imageDownloaded(QString filename);
void imageDownloadFailed(QString filename);
signals:
- void thumbnailChanged(QString filename, QImage thumbnail);
+ void thumbnailChanged(QString filename, QImage thumbnail, duration_t duration);
private:
struct Thumbnail {
QImage img;
@@ -56,9 +56,9 @@ private:
};
Thumbnailer();
- static void addPictureThumbnailToCache(const QString &picture_filename, const QImage &thumbnail);
- static void addVideoThumbnailToCache(const QString &picture_filename, duration_t duration);
- static void addUnknownThumbnailToCache(const QString &picture_filename);
+ Thumbnail addPictureThumbnailToCache(const QString &picture_filename, const QImage &thumbnail);
+ Thumbnail addVideoThumbnailToCache(const QString &picture_filename, duration_t duration);
+ Thumbnail addUnknownThumbnailToCache(const QString &picture_filename);
void recalculate(QString filename);
void processItem(QString filename, bool tryDownload);
Thumbnail getThumbnailFromCache(const QString &picture_filename);
diff --git a/profile-widget/divepixmapitem.cpp b/profile-widget/divepixmapitem.cpp
index d295fb4dd..730cdf606 100644
--- a/profile-widget/divepixmapitem.cpp
+++ b/profile-widget/divepixmapitem.cpp
@@ -42,7 +42,8 @@ void CloseButtonItem::show()
DivePictureItem::DivePictureItem(QGraphicsItem *parent): DivePixmapItem(parent),
canvas(new QGraphicsRectItem(this)),
shadow(new QGraphicsRectItem(this)),
- button(new CloseButtonItem(this))
+ button(new CloseButtonItem(this)),
+ baseZValue(0.0)
{
setFlag(ItemIgnoresTransformations);
setAcceptHoverEvents(true);
@@ -67,6 +68,14 @@ DivePictureItem::DivePictureItem(QGraphicsItem *parent): DivePixmapItem(parent),
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()
{
setVisible(prefs.show_pictures_in_profile);
@@ -85,7 +94,7 @@ void DivePictureItem::setPixmap(const QPixmap &pix)
void DivePictureItem::hoverEnterEvent(QGraphicsSceneHoverEvent*)
{
Animations::scaleTo(this, 1.0);
- setZValue(5);
+ setZValue(baseZValue + 5.0);
button->setOpacity(0);
button->show();
@@ -100,7 +109,7 @@ void DivePictureItem::setFileUrl(const QString &s)
void DivePictureItem::hoverLeaveEvent(QGraphicsSceneHoverEvent*)
{
Animations::scaleTo(this, 0.2);
- setZValue(0);
+ setZValue(baseZValue);
Animations::hide(button);
}
diff --git a/profile-widget/divepixmapitem.h b/profile-widget/divepixmapitem.h
index 6abe410a6..f8b922981 100644
--- a/profile-widget/divepixmapitem.h
+++ b/profile-widget/divepixmapitem.h
@@ -32,6 +32,7 @@ class DivePictureItem : public DivePixmapItem {
public:
DivePictureItem(QGraphicsItem *parent = 0);
void setPixmap(const QPixmap& pix);
+ void setBaseZValue(double z);
public slots:
void settingsChanged();
void removePicture();
@@ -45,6 +46,7 @@ private:
QGraphicsRectItem *canvas;
QGraphicsRectItem *shadow;
CloseButtonItem *button;
+ double baseZValue;
};
#endif // DIVEPIXMAPITEM_H
diff --git a/profile-widget/profilewidget2.cpp b/profile-widget/profilewidget2.cpp
index 11dddaa41..79aa2e876 100644
--- a/profile-widget/profilewidget2.cpp
+++ b/profile-widget/profilewidget2.cpp
@@ -87,6 +87,10 @@ static struct _ItemPos {
_Axis heartBeatWithTankBar;
} 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),
currentState(INVALID),
dataModel(new DivePlotDataModel(this)),
@@ -975,6 +979,21 @@ void ProfileWidget2::fixBackgroundPos()
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
void ProfileWidget2::wheelEvent(QWheelEvent *event)
{
@@ -2064,9 +2083,37 @@ void ProfileWidget2::clearPictures()
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
// 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
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.
int size = Thumbnailer::defaultThumbnailSize();
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.
ProfileWidget2::PictureEntry::PictureEntry(offset_t offsetIn, const QString &filenameIn, QGraphicsScene *scene) : offset(offsetIn),
+ duration(duration_t {0}),
filename(filenameIn),
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);
}
+// 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.
// 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.
// We can't use the z-value, because that will be modified on hoverEnter and hoverExit events.
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;
- for (auto it = pictures.begin(); it != pictures.end(); ++it) {
- if (!it->thumbnail)
- continue;
+ for (PictureEntry &e: pictures) {
// 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.
- double x = it->thumbnail->x();
+ double x = e.thumbnail->x();
double y;
if (lastX >= 0.0 && fabs(x - lastX) < 3 && lastY <= (10 + 14 * 3))
y = lastY + 3;
@@ -2125,18 +2195,10 @@ void ProfileWidget2::calculatePictureYPositions()
y = 10;
lastX = x;
lastY = y;
- it->thumbnail->setY(y);
-
- // 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);
+ e.thumbnail->setY(y);
+ updateDurationLine(e); // If we changed the y-position, we also have to change the duration-line.
}
-
- // 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());
+ updateThumbnailPaintOrder();
}
void ProfileWidget2::updateThumbnailXPos(PictureEntry &e)
diff --git a/profile-widget/profilewidget2.h b/profile-widget/profilewidget2.h
index 2640fb85a..bbad5b7cc 100644
--- a/profile-widget/profilewidget2.h
+++ b/profile-widget/profilewidget2.h
@@ -74,6 +74,7 @@ public:
ProfileWidget2(QWidget *parent = 0);
void resetZoom();
+ void scale(qreal sx, qreal sy);
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 setPrintMode(bool mode, bool grayscale = false);
@@ -127,7 +128,7 @@ slots: // Necessary to call from QAction's signals.
void deleteCurrentDC();
void pointInserted(const QModelIndex &parent, 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? */
void recreatePlannedDive();
@@ -234,14 +235,19 @@ private:
// Pictures that are outside of the dive time are not shown.
struct PictureEntry {
offset_t offset;
+ duration_t duration;
QString filename;
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);
bool operator< (const PictureEntry &e) const;
};
void updateThumbnailXPos(PictureEntry &e);
std::vector<PictureEntry> pictures;
void calculatePictureYPositions();
+ void updateDurationLine(PictureEntry &e);
+ void updateThumbnailPaintOrder();
QList<DiveHandler *> handles;
void repositionDiveHandlers();
diff --git a/qt-models/divepicturemodel.cpp b/qt-models/divepicturemodel.cpp
index c881ce5bf..f0b5eff47 100644
--- a/qt-models/divepicturemodel.cpp
+++ b/qt-models/divepicturemodel.cpp
@@ -165,7 +165,7 @@ int DivePictureModel::findPictureId(const QString &filename)
return -1;
}
-void DivePictureModel::updateThumbnail(QString filename, QImage thumbnail)
+void DivePictureModel::updateThumbnail(QString filename, QImage thumbnail, duration_t)
{
int i = findPictureId(filename);
if (i >= 0) {
diff --git a/qt-models/divepicturemodel.h b/qt-models/divepicturemodel.h
index 6dc633fb2..3bd872d6a 100644
--- a/qt-models/divepicturemodel.h
+++ b/qt-models/divepicturemodel.h
@@ -2,6 +2,8 @@
#ifndef DIVEPICTUREMODEL_H
#define DIVEPICTUREMODEL_H
+#include "core/units.h"
+
#include <QAbstractTableModel>
#include <QImage>
#include <QFuture>
@@ -28,7 +30,7 @@ signals:
void picturesRemoved(const QVector<QString> &fileUrls);
public slots:
void setZoomLevel(int level);
- void updateThumbnail(QString filename, QImage thumbnail);
+ void updateThumbnail(QString filename, QImage thumbnail, duration_t duration);
private:
DivePictureModel();
QVector<PictureEntry> pictures;
diff --git a/subsurface-helper.cpp b/subsurface-helper.cpp
index f37bd332c..e0c3b6353 100644
--- a/subsurface-helper.cpp
+++ b/subsurface-helper.cpp
@@ -31,10 +31,12 @@
#ifndef SUBSURFACE_TEST_DATA
QObject *qqWindowObject = NULL;
+static void register_meta_types();
void init_ui()
{
init_qt_late();
register_qml_types();
+ register_meta_types();
#ifndef SUBSURFACE_MOBILE
PluginManager::instance().loadPlugins();
@@ -137,6 +139,12 @@ void run_ui()
#endif // SUBSURFACE_MOBILE
qApp->exec();
}
+
+Q_DECLARE_METATYPE(duration_t)
+static void register_meta_types()
+{
+ qRegisterMetaType<duration_t>();
+}
#endif // not SUBSURFACE_TEST_DATA
void register_qml_types()