QmlProfiler: Add a binary trace format

Storing traces in binary form is preferable as loading and saving is
faster and the trace files are smaller.

Change-Id: Ia7340ac526d5ce9391b1e32fc48fc1fab3ffa13d
Reviewed-by: Joerg Bornemann <joerg.bornemann@qt.io>
This commit is contained in:
Ulf Hermann
2016-05-10 13:27:48 +02:00
parent d6d69b811a
commit ecd8e764dd
7 changed files with 205 additions and 72 deletions

View File

@@ -40,7 +40,8 @@ const char ANALYZER[] = "Analyzer";
const int QML_MIN_LEVEL = 1; // Set to 0 to remove the empty line between models in the timeline const int QML_MIN_LEVEL = 1; // Set to 0 to remove the empty line between models in the timeline
const char TraceFileExtension[] = ".qtd"; const char QtdFileExtension[] = ".qtd";
const char QztFileExtension[] = ".qzt";
const char QmlProfilerPerspectiveId[] = "QmlProfiler.Perspective"; const char QmlProfilerPerspectiveId[] = "QmlProfiler.Perspective";
const char QmlProfilerTimelineDockId[] = "QmlProfiler.Timeline.Dock"; const char QmlProfilerTimelineDockId[] = "QmlProfiler.Timeline.Dock";

View File

@@ -137,20 +137,19 @@ const QVector<QmlEventType> &QmlProfilerDataModel::eventTypes() const
return d->eventTypes; return d->eventTypes;
} }
void QmlProfilerDataModel::setData(qint64 traceStart, qint64 traceEnd, void QmlProfilerDataModel::setEventTypes(const QVector<QmlEventType> &types)
const QVector<QmlEventType> &types,
const QVector<QmlEvent> &events)
{ {
Q_D(QmlProfilerDataModel); Q_D(QmlProfilerDataModel);
d->modelManager->traceTime()->setTime(traceStart, traceEnd);
d->eventTypes = types; d->eventTypes = types;
for (int id = 0; id < types.count(); ++id) for (int id = 0; id < types.count(); ++id)
d->eventTypeIds[types[id]] = id; d->eventTypeIds[types[id]] = id;
}
foreach (const QmlEvent &event, events) { void QmlProfilerDataModel::addEvent(const QmlEvent &event)
{
Q_D(QmlProfilerDataModel);
d->modelManager->dispatch(event, d->eventTypes[event.typeIndex()]); d->modelManager->dispatch(event, d->eventTypes[event.typeIndex()]);
d->eventStream << event; d->eventStream << event;
}
} }
void QmlProfilerDataModel::clear() void QmlProfilerDataModel::clear()

View File

@@ -46,11 +46,11 @@ public:
~QmlProfilerDataModel(); ~QmlProfilerDataModel();
const QVector<QmlEventType> &eventTypes() const; const QVector<QmlEventType> &eventTypes() const;
void setData(qint64 traceStart, qint64 traceEnd, const QVector<QmlEventType> &types, void setEventTypes(const QVector<QmlEventType> &types);
const QVector<QmlEvent> &events);
void clear(); void clear();
bool isEmpty() const; bool isEmpty() const;
void addEvent(const QmlEvent &event);
void addTypedEvent(const QmlEvent &event, const QmlEventType &type); void addTypedEvent(const QmlEvent &event, const QmlEventType &type);
void replayEvents(qint64 startTime, qint64 endTime, void replayEvents(qint64 startTime, qint64 endTime,
QmlProfilerModelManager::EventLoader loader) const; QmlProfilerModelManager::EventLoader loader) const;

View File

@@ -310,7 +310,10 @@ void QmlProfilerModelManager::save(const QString &filename)
QFuture<void> result = Utils::runAsync([file, writer] (QFutureInterface<void> &future) { QFuture<void> result = Utils::runAsync([file, writer] (QFutureInterface<void> &future) {
writer->setFuture(&future); writer->setFuture(&future);
writer->save(file); if (file->fileName().endsWith(QLatin1String(Constants::QtdFileExtension)))
writer->saveQtd(file);
else
writer->saveQzt(file);
delete writer; delete writer;
file->deleteLater(); file->deleteLater();
}); });
@@ -321,8 +324,9 @@ void QmlProfilerModelManager::save(const QString &filename)
void QmlProfilerModelManager::load(const QString &filename) void QmlProfilerModelManager::load(const QString &filename)
{ {
bool isQtd = filename.endsWith(QLatin1String(Constants::QtdFileExtension));
QFile *file = new QFile(filename, this); QFile *file = new QFile(filename, this);
if (!file->open(QIODevice::ReadOnly | QIODevice::Text)) { if (!file->open(isQtd ? (QIODevice::ReadOnly | QIODevice::Text) : QIODevice::ReadOnly)) {
emit error(tr("Could not open %1 for reading.").arg(filename)); emit error(tr("Could not open %1 for reading.").arg(filename));
delete file; delete file;
emit loadFinished(); emit loadFinished();
@@ -338,19 +342,28 @@ void QmlProfilerModelManager::load(const QString &filename)
emit error(message); emit error(message);
}, Qt::QueuedConnection); }, Qt::QueuedConnection);
connect(reader, &QmlProfilerFileReader::typesLoaded,
d->model, &QmlProfilerDataModel::setEventTypes);
connect(reader, &QmlProfilerFileReader::notesLoaded,
d->notesModel, &QmlProfilerNotesModel::setNotes);
connect(reader, &QmlProfilerFileReader::qmlEventLoaded,
d->model, &QmlProfilerDataModel::addEvent);
connect(reader, &QmlProfilerFileReader::success, this, [this, reader]() { connect(reader, &QmlProfilerFileReader::success, this, [this, reader]() {
d->model->setData(reader->traceStart(), qMax(reader->traceStart(), reader->traceEnd()), d->traceTime->setTime(reader->traceStart(), reader->traceEnd());
reader->eventTypes(), reader->events());
d->notesModel->setNotes(reader->notes());
setRecordedFeatures(reader->loadedFeatures()); setRecordedFeatures(reader->loadedFeatures());
d->traceTime->increaseEndTime(reader->events().last().timestamp());
delete reader; delete reader;
acquiringDone(); acquiringDone();
}, Qt::QueuedConnection); }, Qt::QueuedConnection);
QFuture<void> result = Utils::runAsync([file, reader] (QFutureInterface<void> &future) { QFuture<void> result = Utils::runAsync([isQtd, file, reader] (QFutureInterface<void> &future) {
reader->setFuture(&future); reader->setFuture(&future);
reader->load(file); if (isQtd)
reader->loadQtd(file);
else
reader->loadQzt(file);
file->close(); file->close();
file->deleteLater(); file->deleteLater();
}); });

View File

@@ -615,13 +615,15 @@ void saveLastTraceFile(const QString &filename)
void QmlProfilerTool::showSaveDialog() void QmlProfilerTool::showSaveDialog()
{ {
QLatin1String tFile(QtdFileExtension);
QLatin1String zFile(QztFileExtension);
QString filename = QFileDialog::getSaveFileName( QString filename = QFileDialog::getSaveFileName(
ICore::mainWindow(), tr("Save QML Trace"), ICore::mainWindow(), tr("Save QML Trace"),
QmlProfilerPlugin::globalSettings()->lastTraceFile(), QmlProfilerPlugin::globalSettings()->lastTraceFile(),
tr("QML traces (*%1)").arg(QLatin1String(TraceFileExtension))); tr("QML traces (*%1 *%2)").arg(zFile).arg(tFile));
if (!filename.isEmpty()) { if (!filename.isEmpty()) {
if (!filename.endsWith(QLatin1String(TraceFileExtension))) if (!filename.endsWith(zFile) && !filename.endsWith(tFile))
filename += QLatin1String(TraceFileExtension); filename += zFile;
saveLastTraceFile(filename); saveLastTraceFile(filename);
Debugger::enableMainWindow(false); Debugger::enableMainWindow(false);
d->m_profilerModelManager->save(filename); d->m_profilerModelManager->save(filename);
@@ -635,10 +637,12 @@ void QmlProfilerTool::showLoadDialog()
Debugger::selectPerspective(QmlProfilerPerspectiveId); Debugger::selectPerspective(QmlProfilerPerspectiveId);
QLatin1String tFile(QtdFileExtension);
QLatin1String zFile(QztFileExtension);
QString filename = QFileDialog::getOpenFileName( QString filename = QFileDialog::getOpenFileName(
ICore::mainWindow(), tr("Load QML Trace"), ICore::mainWindow(), tr("Load QML Trace"),
QmlProfilerPlugin::globalSettings()->lastTraceFile(), QmlProfilerPlugin::globalSettings()->lastTraceFile(),
tr("QML traces (*%1)").arg(QLatin1String(TraceFileExtension))); tr("QML traces (*%1 *%2)").arg(zFile).arg(tFile));
if (!filename.isEmpty()) { if (!filename.isEmpty()) {
saveLastTraceFile(filename); saveLastTraceFile(filename);

View File

@@ -33,6 +33,9 @@
#include <QXmlStreamWriter> #include <QXmlStreamWriter>
#include <QStack> #include <QStack>
#include <QDebug> #include <QDebug>
#include <QFile>
#include <QBuffer>
#include <QDataStream>
namespace QmlProfiler { namespace QmlProfiler {
namespace Internal { namespace Internal {
@@ -119,6 +122,12 @@ QmlProfilerFileReader::QmlProfilerFileReader(QObject *parent) :
m_future(0), m_future(0),
m_loadedFeatures(0) m_loadedFeatures(0)
{ {
static int meta[] = {
qRegisterMetaType<QmlEvent>(),
qRegisterMetaType<QVector<QmlEventType> >(),
qRegisterMetaType<QVector<QmlNote> >()
};
Q_UNUSED(meta);
} }
void QmlProfilerFileReader::setFuture(QFutureInterface<void> *future) void QmlProfilerFileReader::setFuture(QFutureInterface<void> *future)
@@ -126,7 +135,7 @@ void QmlProfilerFileReader::setFuture(QFutureInterface<void> *future)
m_future = future; m_future = future;
} }
bool QmlProfilerFileReader::load(QIODevice *device) bool QmlProfilerFileReader::loadQtd(QIODevice *device)
{ {
if (m_future) { if (m_future) {
m_future->setProgressRange(0, 1000); m_future->setProgressRange(0, 1000);
@@ -159,6 +168,7 @@ bool QmlProfilerFileReader::load(QIODevice *device)
if (elementName == _("eventData")) { if (elementName == _("eventData")) {
loadEventTypes(stream); loadEventTypes(stream);
emit typesLoaded(m_eventTypes);
break; break;
} }
@@ -169,6 +179,7 @@ bool QmlProfilerFileReader::load(QIODevice *device)
if (elementName == _("noteData")) { if (elementName == _("noteData")) {
loadNotes(stream); loadNotes(stream);
emit notesLoaded(m_notes);
break; break;
} }
@@ -182,47 +193,98 @@ bool QmlProfilerFileReader::load(QIODevice *device)
emit error(tr("Error while parsing trace data file: %1").arg(stream.errorString())); emit error(tr("Error while parsing trace data file: %1").arg(stream.errorString()));
return false; return false;
} else { } else {
std::sort(m_events.begin(), m_events.end(), [](const QmlEvent &a, const QmlEvent &b) {
return a.timestamp() < b.timestamp();
});
emit success(); emit success();
return true; return true;
} }
} }
bool QmlProfilerFileReader::loadQzt(QIODevice *device)
{
if (m_future) {
m_future->setProgressRange(0, 1000);
m_future->setProgressValue(0);
}
QDataStream stream(device);
stream.setVersion(QDataStream::Qt_5_5);
QByteArray magic;
stream >> magic;
if (magic != QByteArray("QMLPROFILER")) {
emit error(tr("Invalid magic: %1").arg(QLatin1String(magic)));
return false;
}
qint32 dataStreamVersion;
stream >> dataStreamVersion;
if (dataStreamVersion > QDataStream::Qt_DefaultCompiledVersion) {
emit error(tr("Unknown data stream version: %1").arg(dataStreamVersion));
return false;
}
stream.setVersion(dataStreamVersion);
stream >> m_traceStart >> m_traceEnd;
QBuffer buffer;
QDataStream bufferStream(&buffer);
bufferStream.setVersion(dataStreamVersion);
QByteArray data;
updateProgress(device);
stream >> data;
buffer.setData(qUncompress(data));
buffer.open(QIODevice::ReadOnly);
bufferStream >> m_eventTypes;
buffer.close();
emit typesLoaded(m_eventTypes);
updateProgress(device);
stream >> data;
buffer.setData(qUncompress(data));
buffer.open(QIODevice::ReadOnly);
bufferStream >> m_notes;
buffer.close();
emit notesLoaded(m_notes);
updateProgress(device);
QmlEvent event;
while (!stream.atEnd()) {
stream >> data;
buffer.setData(qUncompress(data));
buffer.open(QIODevice::ReadOnly);
while (!buffer.atEnd()) {
if (isCanceled())
return false;
bufferStream >> event;
if (bufferStream.status() == QDataStream::Ok) {
if (event.typeIndex() >= m_eventTypes.length()) {
emit error(tr("Invalid type index %1").arg(event.typeIndex()));
return false;
}
m_loadedFeatures |= (1ULL << m_eventTypes[event.typeIndex()].feature());
emit qmlEventLoaded(event);
} else if (bufferStream.status() == QDataStream::ReadPastEnd) {
break; // Apparently EOF is a character so we end up here after the last event.
} else if (bufferStream.status() == QDataStream::ReadCorruptData) {
emit error(tr("Corrupt data before position %1.").arg(device->pos()));
return false;
} else {
Q_UNREACHABLE();
}
}
buffer.close();
updateProgress(device);
}
emit success();
return true;
}
quint64 QmlProfilerFileReader::loadedFeatures() const quint64 QmlProfilerFileReader::loadedFeatures() const
{ {
return m_loadedFeatures; return m_loadedFeatures;
} }
ProfileFeature featureFromType(const QmlEventType &type) {
if (type.rangeType < MaximumRangeType)
return featureFromRangeType(type.rangeType);
switch (type.message) {
case Event:
switch (type.detailType) {
case AnimationFrame:
return ProfileAnimations;
case Key:
case Mouse:
return ProfileInputEvents;
default:
return MaximumProfileFeature;
}
case PixmapCacheEvent:
return ProfilePixmapCache;
case SceneGraphFrame:
return ProfileSceneGraph;
case MemoryAllocation:
return ProfileMemory;
case DebugMessage:
return ProfileDebugMessages;
default:
return MaximumProfileFeature;
}
}
void QmlProfilerFileReader::loadEventTypes(QXmlStreamReader &stream) void QmlProfilerFileReader::loadEventTypes(QXmlStreamReader &stream)
{ {
QTC_ASSERT(stream.name() == _("eventData"), return); QTC_ASSERT(stream.name() == _("eventData"), return);
@@ -248,7 +310,7 @@ void QmlProfilerFileReader::loadEventTypes(QXmlStreamReader &stream)
switch (token) { switch (token) {
case QXmlStreamReader::StartElement: { case QXmlStreamReader::StartElement: {
if (elementName == _("event")) { if (elementName == _("event")) {
progress(stream.device()); updateProgress(stream.device());
type = defaultEvent; type = defaultEvent;
const QXmlStreamAttributes attributes = stream.attributes(); const QXmlStreamAttributes attributes = stream.attributes();
@@ -331,7 +393,7 @@ void QmlProfilerFileReader::loadEventTypes(QXmlStreamReader &stream)
if (typeIndex >= m_eventTypes.size()) if (typeIndex >= m_eventTypes.size())
m_eventTypes.resize(typeIndex + 1); m_eventTypes.resize(typeIndex + 1);
m_eventTypes[typeIndex] = type; m_eventTypes[typeIndex] = type;
ProfileFeature feature = featureFromType(type); ProfileFeature feature = type.feature();
if (feature != MaximumProfileFeature) if (feature != MaximumProfileFeature)
m_loadedFeatures |= (1ULL << static_cast<uint>(feature)); m_loadedFeatures |= (1ULL << static_cast<uint>(feature));
} }
@@ -352,6 +414,7 @@ void QmlProfilerFileReader::loadEventTypes(QXmlStreamReader &stream)
void QmlProfilerFileReader::loadEvents(QXmlStreamReader &stream) void QmlProfilerFileReader::loadEvents(QXmlStreamReader &stream)
{ {
QTC_ASSERT(stream.name() == _("profilerDataModel"), return); QTC_ASSERT(stream.name() == _("profilerDataModel"), return);
QVector<QmlEvent> events;
while (!stream.atEnd() && !stream.hasError()) { while (!stream.atEnd() && !stream.hasError()) {
if (isCanceled()) if (isCanceled())
@@ -363,7 +426,7 @@ void QmlProfilerFileReader::loadEvents(QXmlStreamReader &stream)
switch (token) { switch (token) {
case QXmlStreamReader::StartElement: { case QXmlStreamReader::StartElement: {
if (elementName == _("range")) { if (elementName == _("range")) {
progress(stream.device()); updateProgress(stream.device());
QmlEvent event; QmlEvent event;
const QXmlStreamAttributes attributes = stream.attributes(); const QXmlStreamAttributes attributes = stream.attributes();
@@ -378,12 +441,12 @@ void QmlProfilerFileReader::loadEvents(QXmlStreamReader &stream)
if (attributes.hasAttribute(_("duration"))) { if (attributes.hasAttribute(_("duration"))) {
event.setRangeStage(RangeStart); event.setRangeStage(RangeStart);
m_events.append(event); events.append(event);
QmlEvent rangeEnd(event); QmlEvent rangeEnd(event);
rangeEnd.setRangeStage(RangeEnd); rangeEnd.setRangeStage(RangeEnd);
rangeEnd.setTimestamp(event.timestamp() rangeEnd.setTimestamp(event.timestamp()
+ attributes.value(_("duration")).toLongLong()); + attributes.value(_("duration")).toLongLong());
m_events.append(rangeEnd); events.append(rangeEnd);
} else { } else {
// attributes for special events // attributes for special events
if (attributes.hasAttribute(_("framerate"))) if (attributes.hasAttribute(_("framerate")))
@@ -419,7 +482,7 @@ void QmlProfilerFileReader::loadEvents(QXmlStreamReader &stream)
if (attributes.hasAttribute(_("text"))) if (attributes.hasAttribute(_("text")))
event.setString(attributes.value(_("text")).toString()); event.setString(attributes.value(_("text")).toString());
m_events.append(event); events.append(event);
} }
} }
break; break;
@@ -427,6 +490,11 @@ void QmlProfilerFileReader::loadEvents(QXmlStreamReader &stream)
case QXmlStreamReader::EndElement: { case QXmlStreamReader::EndElement: {
if (elementName == _("profilerDataModel")) { if (elementName == _("profilerDataModel")) {
// done reading profilerDataModel // done reading profilerDataModel
std::sort(events.begin(), events.end(), [](const QmlEvent &a, const QmlEvent &b) {
return a.timestamp() < b.timestamp();
});
foreach (const QmlEvent &event, events)
emit qmlEventLoaded(event);
return; return;
} }
break; break;
@@ -449,7 +517,7 @@ void QmlProfilerFileReader::loadNotes(QXmlStreamReader &stream)
switch (token) { switch (token) {
case QXmlStreamReader::StartElement: { case QXmlStreamReader::StartElement: {
if (elementName == _("note")) { if (elementName == _("note")) {
progress(stream.device()); updateProgress(stream.device());
QXmlStreamAttributes attrs = stream.attributes(); QXmlStreamAttributes attrs = stream.attributes();
currentNote.startTime = attrs.value(_("startTime")).toLongLong(); currentNote.startTime = attrs.value(_("startTime")).toLongLong();
currentNote.duration = attrs.value(_("duration")).toLongLong(); currentNote.duration = attrs.value(_("duration")).toLongLong();
@@ -475,7 +543,7 @@ void QmlProfilerFileReader::loadNotes(QXmlStreamReader &stream)
} }
} }
void QmlProfilerFileReader::progress(QIODevice *device) void QmlProfilerFileReader::updateProgress(QIODevice *device)
{ {
if (!m_future) if (!m_future)
return; return;
@@ -519,7 +587,7 @@ void QmlProfilerFileWriter::setFuture(QFutureInterface<void> *future)
m_future = future; m_future = future;
} }
void QmlProfilerFileWriter::save(QIODevice *device) void QmlProfilerFileWriter::saveQtd(QIODevice *device)
{ {
if (m_future) { if (m_future) {
m_future->setProgressRange(0, qMax(m_model->eventTypes().size() + m_notes.size(), 1)); m_future->setProgressRange(0, qMax(m_model->eventTypes().size() + m_notes.size(), 1));
@@ -689,6 +757,52 @@ void QmlProfilerFileWriter::save(QIODevice *device)
stream.writeEndDocument(); stream.writeEndDocument();
} }
void QmlProfilerFileWriter::saveQzt(QFile *file)
{
QDataStream stream(file);
stream.setVersion(QDataStream::Qt_5_5);
stream << QByteArray("QMLPROFILER");
stream << static_cast<qint32>(QDataStream::Qt_DefaultCompiledVersion);
stream.setVersion(QDataStream::Qt_DefaultCompiledVersion);
stream << m_startTime << m_endTime;
QBuffer buffer;
QDataStream bufferStream(&buffer);
buffer.open(QIODevice::WriteOnly);
incrementProgress();
buffer.open(QIODevice::WriteOnly);
bufferStream << m_model->eventTypes();
stream << qCompress(buffer.data());
buffer.close();
buffer.buffer().clear();
incrementProgress();
buffer.open(QIODevice::WriteOnly);
bufferStream << m_notes;
stream << qCompress(buffer.data());
buffer.close();
buffer.buffer().clear();
incrementProgress();
buffer.open(QIODevice::WriteOnly);
m_model->replayEvents(-1, -1, [&stream, &buffer, &bufferStream](const QmlEvent &event,
const QmlEventType &type) {
Q_UNUSED(type);
bufferStream << event;
// 32MB buffer should be plenty for efficient compression
if (buffer.data().length() > (1 << 25)) {
stream << qCompress(buffer.data());
buffer.close();
buffer.buffer().clear();
buffer.open(QIODevice::WriteOnly);
}
});
stream << qCompress(buffer.data());
buffer.close();
buffer.buffer().clear();
}
void QmlProfilerFileWriter::incrementProgress() void QmlProfilerFileWriter::incrementProgress()
{ {
if (!m_future) if (!m_future)

View File

@@ -35,6 +35,7 @@
#include <QVector> #include <QVector>
#include <QString> #include <QString>
QT_FORWARD_DECLARE_CLASS(QFile)
QT_FORWARD_DECLARE_CLASS(QIODevice) QT_FORWARD_DECLARE_CLASS(QIODevice)
QT_FORWARD_DECLARE_CLASS(QXmlStreamReader) QT_FORWARD_DECLARE_CLASS(QXmlStreamReader)
@@ -50,17 +51,18 @@ public:
void setFuture(QFutureInterface<void> *future); void setFuture(QFutureInterface<void> *future);
bool load(QIODevice *device); bool loadQtd(QIODevice *device);
bool loadQzt(QIODevice *device);
quint64 loadedFeatures() const; quint64 loadedFeatures() const;
qint64 traceStart() const { return m_traceStart; } qint64 traceStart() const { return m_traceStart; }
qint64 traceEnd() const { return m_traceEnd; } qint64 traceEnd() const { return m_traceEnd; }
const QVector<QmlEventType> &eventTypes() const { return m_eventTypes; }
const QVector<QmlEvent> &events() const { return m_events; }
const QVector<QmlNote> &notes() const { return m_notes; }
signals: signals:
void typesLoaded(const QVector<QmlProfiler::QmlEventType> &types);
void notesLoaded(const QVector<QmlProfiler::QmlNote> &notes);
void qmlEventLoaded(const QmlProfiler::QmlEvent &event);
void error(const QString &error); void error(const QString &error);
void success(); void success();
@@ -68,13 +70,12 @@ private:
void loadEventTypes(QXmlStreamReader &reader); void loadEventTypes(QXmlStreamReader &reader);
void loadEvents(QXmlStreamReader &reader); void loadEvents(QXmlStreamReader &reader);
void loadNotes(QXmlStreamReader &reader); void loadNotes(QXmlStreamReader &reader);
void progress(QIODevice *device); void updateProgress(QIODevice *device);
bool isCanceled() const; bool isCanceled() const;
qint64 m_traceStart, m_traceEnd; qint64 m_traceStart, m_traceEnd;
QFutureInterface<void> *m_future; QFutureInterface<void> *m_future;
QVector<QmlEventType> m_eventTypes; QVector<QmlEventType> m_eventTypes;
QVector<QmlEvent> m_events;
QVector<QmlNote> m_notes; QVector<QmlNote> m_notes;
quint64 m_loadedFeatures; quint64 m_loadedFeatures;
}; };
@@ -92,7 +93,8 @@ public:
void setNotes(const QVector<QmlNote> &notes); void setNotes(const QVector<QmlNote> &notes);
void setFuture(QFutureInterface<void> *future); void setFuture(QFutureInterface<void> *future);
void save(QIODevice *device); void saveQtd(QIODevice *device);
void saveQzt(QFile *file);
private: private:
void calculateMeasuredTime(); void calculateMeasuredTime();