From 1f76525c5f701f612b56a49715cda6597e3727b1 Mon Sep 17 00:00:00 2001 From: Christiaan Janssen Date: Thu, 8 Mar 2012 16:50:14 +0100 Subject: QmlProfiler: updated standalone app Using a stripped-down version of the profiler data structure. Change-Id: I93a0b12462edea0ca8a1d0db42aa892aa2afc919 Reviewed-by: Kai Koehne --- tools/qmlprofiler/constants.h | 11 +- tools/qmlprofiler/profileclient.cpp | 300 ---- tools/qmlprofiler/profileclient.h | 139 -- tools/qmlprofiler/profiledata.cpp | 1902 -------------------------- tools/qmlprofiler/profiledata.h | 247 ---- tools/qmlprofiler/qmlprofiler.pro | 11 +- tools/qmlprofiler/qmlprofilerapplication.cpp | 22 +- tools/qmlprofiler/qmlprofilerapplication.h | 9 +- tools/qmlprofiler/qmlprofilerclient.cpp | 300 ++++ tools/qmlprofiler/qmlprofilerclient.h | 138 ++ tools/qmlprofiler/qmlprofilerdata.cpp | 602 ++++++++ tools/qmlprofiler/qmlprofilerdata.h | 104 ++ tools/qmlprofiler/qmlprofilereventlocation.h | 61 + 13 files changed, 1228 insertions(+), 2618 deletions(-) delete mode 100644 tools/qmlprofiler/profileclient.cpp delete mode 100644 tools/qmlprofiler/profileclient.h delete mode 100644 tools/qmlprofiler/profiledata.cpp delete mode 100644 tools/qmlprofiler/profiledata.h create mode 100644 tools/qmlprofiler/qmlprofilerclient.cpp create mode 100644 tools/qmlprofiler/qmlprofilerclient.h create mode 100644 tools/qmlprofiler/qmlprofilerdata.cpp create mode 100644 tools/qmlprofiler/qmlprofilerdata.h create mode 100644 tools/qmlprofiler/qmlprofilereventlocation.h (limited to 'tools') diff --git a/tools/qmlprofiler/constants.h b/tools/qmlprofiler/constants.h index e5a1f8025d..d87bad18b1 100644 --- a/tools/qmlprofiler/constants.h +++ b/tools/qmlprofiler/constants.h @@ -54,15 +54,6 @@ const char CMD_RECORD2[] ="r"; const char CMD_QUIT[] ="quit"; const char CMD_QUIT2[] = "q"; -const char TYPE_PAINTING_STR[] = "Painting"; -const char TYPE_COMPILING_STR[] = "Compiling"; -const char TYPE_CREATING_STR[] = "Creating"; -const char TYPE_BINDING_STR[] = "Binding"; -const char TYPE_HANDLINGSIGNAL_STR[] = "HandlingSignal"; -const char PROFILER_FILE_VERSION[] = "1.02"; - -const int MIN_LEVEL = 1; - -} // Contants +} // Constants #endif // CONSTANTS_H diff --git a/tools/qmlprofiler/profileclient.cpp b/tools/qmlprofiler/profileclient.cpp deleted file mode 100644 index 85287464cf..0000000000 --- a/tools/qmlprofiler/profileclient.cpp +++ /dev/null @@ -1,300 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/ -** -** This file is part of the QtQml module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** GNU Lesser General Public License Usage -** This file may be used under the terms of the GNU Lesser General Public -** License version 2.1 as published by the Free Software Foundation and -** appearing in the file LICENSE.LGPL included in the packaging of this -** file. Please review the following information to ensure the GNU Lesser -** General Public License version 2.1 requirements will be met: -** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Nokia gives you certain additional -** rights. These rights are described in the Nokia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU General -** Public License version 3.0 as published by the Free Software Foundation -** and appearing in the file LICENSE.GPL included in the packaging of this -** file. Please review the following information to ensure the GNU General -** Public License version 3.0 requirements will be met: -** http://www.gnu.org/copyleft/gpl.html. -** -** Other Usage -** Alternatively, this file may be used in accordance with the terms and -** conditions contained in a signed written agreement between you and Nokia. -** -** -** -** -** -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "profileclient.h" - -#include -#include - -ProfileClient::ProfileClient(const QString &clientName, - QQmlDebugConnection *client) - : QQmlDebugClient(clientName, client), - m_recording(false), - m_enabled(false) -{ -} - -ProfileClient::~ProfileClient() -{ - //Disable profiling if started by client - //Profiling data will be lost!! - if (isRecording()) - setRecording(false); -} - -void ProfileClient::clearData() -{ - emit cleared(); -} - -bool ProfileClient::isEnabled() const -{ - return m_enabled; -} - -void ProfileClient::sendRecordingStatus() -{ -} - -bool ProfileClient::isRecording() const -{ - return m_recording; -} - -void ProfileClient::setRecording(bool v) -{ - if (v == m_recording) - return; - - m_recording = v; - - if (state() == Enabled) { - sendRecordingStatus(); - } - - emit recordingChanged(v); -} - -void ProfileClient::stateChanged(State status) -{ - if ((m_enabled && status != Enabled) || - (!m_enabled && status == Enabled)) - emit enabledChanged(); - - m_enabled = status == Enabled; - -} - -class QmlProfileClientPrivate -{ -public: - QmlProfileClientPrivate() - : inProgressRanges(0) - , maximumTime(0) - { - ::memset(rangeCount, 0, - QQmlProfilerService::MaximumRangeType * sizeof(int)); - } - - qint64 inProgressRanges; - QStack rangeStartTimes[QQmlProfilerService::MaximumRangeType]; - QStack rangeDatas[QQmlProfilerService::MaximumRangeType]; - QStack rangeLocations[QQmlProfilerService::MaximumRangeType]; - int rangeCount[QQmlProfilerService::MaximumRangeType]; - qint64 maximumTime; -}; - -QmlProfileClient::QmlProfileClient( - QQmlDebugConnection *client) - : ProfileClient(QLatin1String("CanvasFrameRate"), client), - d(new QmlProfileClientPrivate) -{ -} - -QmlProfileClient::~QmlProfileClient() -{ - delete d; -} - -void QmlProfileClient::clearData() -{ - ::memset(d->rangeCount, 0, - QQmlProfilerService::MaximumRangeType * sizeof(int)); - ProfileClient::clearData(); -} - -void QmlProfileClient::sendRecordingStatus() -{ - QByteArray ba; - QDataStream stream(&ba, QIODevice::WriteOnly); - stream << isRecording(); - sendMessage(ba); -} - -void QmlProfileClient::messageReceived(const QByteArray &data) -{ - QByteArray rwData = data; - QDataStream stream(&rwData, QIODevice::ReadOnly); - - qint64 time; - int messageType; - - stream >> time >> messageType; - - if (messageType >= QQmlProfilerService::MaximumMessage) - return; - - if (messageType == QQmlProfilerService::Event) { - int event; - stream >> event; - - if (event == QQmlProfilerService::EndTrace) { - emit this->traceFinished(time); - d->maximumTime = time; - d->maximumTime = qMax(time, d->maximumTime); - } else if (event == QQmlProfilerService::AnimationFrame) { - int frameRate, animationCount; - stream >> frameRate >> animationCount; - emit this->frame(time, frameRate, animationCount); - d->maximumTime = qMax(time, d->maximumTime); - } else if (event == QQmlProfilerService::StartTrace) { - emit this->traceStarted(time); - d->maximumTime = time; - } else if (event < QQmlProfilerService::MaximumEventType) { - d->maximumTime = qMax(time, d->maximumTime); - } - } else if (messageType == QQmlProfilerService::Complete) { - emit complete(); - - } else { - int range; - stream >> range; - - if (range >= QQmlProfilerService::MaximumRangeType) - return; - - if (messageType == QQmlProfilerService::RangeStart) { - d->rangeStartTimes[range].push(time); - d->inProgressRanges |= (static_cast(1) << range); - ++d->rangeCount[range]; - } else if (messageType == QQmlProfilerService::RangeData) { - QString data; - stream >> data; - - int count = d->rangeCount[range]; - if (count > 0) { - while (d->rangeDatas[range].count() < count) - d->rangeDatas[range].push(QStringList()); - d->rangeDatas[range][count-1] << data; - } - - } else if (messageType == QQmlProfilerService::RangeLocation) { - QString fileName; - int line; - int column = -1; - stream >> fileName >> line; - - if (!stream.atEnd()) - stream >> column; - - if (d->rangeCount[range] > 0) { - d->rangeLocations[range].push(EventLocation(fileName, line, - column)); - } - } else { - if (d->rangeCount[range] > 0) { - --d->rangeCount[range]; - if (d->inProgressRanges & (static_cast(1) << range)) - d->inProgressRanges &= ~(static_cast(1) << range); - - d->maximumTime = qMax(time, d->maximumTime); - QStringList data = d->rangeDatas[range].count() ? - d->rangeDatas[range].pop() : QStringList(); - EventLocation location = d->rangeLocations[range].count() ? - d->rangeLocations[range].pop() : EventLocation(); - - qint64 startTime = d->rangeStartTimes[range].pop(); - emit this->range((QQmlProfilerService::RangeType)range, - startTime, time - startTime, data, location); - if (d->rangeCount[range] == 0) { - int count = d->rangeDatas[range].count() + - d->rangeStartTimes[range].count() + - d->rangeLocations[range].count(); - if (count != 0) - qWarning() << "incorrectly nested data"; - } - } - } - } -} - -V8ProfileClient::V8ProfileClient(QQmlDebugConnection *client) - : ProfileClient(QLatin1String("V8Profiler"), client) -{ -} - -V8ProfileClient::~V8ProfileClient() -{ -} - -void V8ProfileClient::sendRecordingStatus() -{ - QByteArray ba; - QDataStream stream(&ba, QIODevice::WriteOnly); - QByteArray cmd("V8PROFILER"); - QByteArray option(""); - QByteArray title(""); - - if (m_recording) { - option = "start"; - } else { - option = "stop"; - } - stream << cmd << option << title; - sendMessage(ba); -} - -void V8ProfileClient::messageReceived(const QByteArray &data) -{ - QByteArray rwData = data; - QDataStream stream(&rwData, QIODevice::ReadOnly); - - int messageType; - - stream >> messageType; - - if (messageType == V8Complete) { - emit complete(); - } else if (messageType == V8Entry) { - QString filename; - QString function; - int lineNumber; - double totalTime; - double selfTime; - int depth; - - stream >> filename >> function >> lineNumber >> totalTime >> - selfTime >> depth; - emit this->range(depth, function, filename, lineNumber, totalTime, - selfTime); - } -} - diff --git a/tools/qmlprofiler/profileclient.h b/tools/qmlprofiler/profileclient.h deleted file mode 100644 index 54826a291a..0000000000 --- a/tools/qmlprofiler/profileclient.h +++ /dev/null @@ -1,139 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/ -** -** This file is part of the QtQml module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** GNU Lesser General Public License Usage -** This file may be used under the terms of the GNU Lesser General Public -** License version 2.1 as published by the Free Software Foundation and -** appearing in the file LICENSE.LGPL included in the packaging of this -** file. Please review the following information to ensure the GNU Lesser -** General Public License version 2.1 requirements will be met: -** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Nokia gives you certain additional -** rights. These rights are described in the Nokia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU General -** Public License version 3.0 as published by the Free Software Foundation -** and appearing in the file LICENSE.GPL included in the packaging of this -** file. Please review the following information to ensure the GNU General -** Public License version 3.0 requirements will be met: -** http://www.gnu.org/copyleft/gpl.html. -** -** Other Usage -** Alternatively, this file may be used in accordance with the terms and -** conditions contained in a signed written agreement between you and Nokia. -** -** -** -** -** -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef PROFILECLIENT_H -#define PROFILECLIENT_H - -#include "profiledata.h" - -#include "qqmldebugclient.h" -#include - -class ProfileClientPrivate; -class ProfileClient : public QQmlDebugClient -{ - Q_OBJECT - - Q_PROPERTY(bool enabled READ isEnabled NOTIFY enabledChanged) - Q_PROPERTY(bool recording READ isRecording WRITE setRecording - NOTIFY recordingChanged) - -public: - ProfileClient(const QString & clientName, - QQmlDebugConnection *client); - ~ProfileClient(); - - bool isEnabled() const; - bool isRecording() const; - -public slots: - void setRecording(bool); - virtual void clearData(); - virtual void sendRecordingStatus(); - -signals: - void complete(); - void recordingChanged(bool arg); - void enabledChanged(); - void cleared(); - -protected: - virtual void stateChanged(State); - -protected: - bool m_recording; - bool m_enabled; -}; - -class QmlProfileClient : public ProfileClient -{ - Q_OBJECT - -public: - QmlProfileClient(QQmlDebugConnection *client); - ~QmlProfileClient(); - -public slots: - void clearData(); - void sendRecordingStatus(); - -signals: - void traceFinished( qint64 time ); - void traceStarted( qint64 time ); - void range(QQmlProfilerService::RangeType type, qint64 startTime, - qint64 length, const QStringList &data, - const EventLocation &location); - void frame(qint64 time, int frameRate, int animationCount); - -protected: - virtual void messageReceived(const QByteArray &); - -private: - class QmlProfileClientPrivate *d; -}; - -class V8ProfileClient : public ProfileClient -{ - Q_OBJECT - -public: - enum Message { - V8Entry, - V8Complete, - - V8MaximumMessage - }; - - V8ProfileClient(QQmlDebugConnection *client); - ~V8ProfileClient(); - -public slots: - void sendRecordingStatus(); - -signals: - void range(int depth, const QString &function, const QString &filename, - int lineNumber, double totalTime, double selfTime); - -protected: - virtual void messageReceived(const QByteArray &); -}; - -#endif // PROFILECLIENT_H diff --git a/tools/qmlprofiler/profiledata.cpp b/tools/qmlprofiler/profiledata.cpp deleted file mode 100644 index 6082f4aa94..0000000000 --- a/tools/qmlprofiler/profiledata.cpp +++ /dev/null @@ -1,1902 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/ -** -** This file is part of the QtQml module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** GNU Lesser General Public License Usage -** This file may be used under the terms of the GNU Lesser General Public -** License version 2.1 as published by the Free Software Foundation and -** appearing in the file LICENSE.LGPL included in the packaging of this -** file. Please review the following information to ensure the GNU Lesser -** General Public License version 2.1 requirements will be met: -** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Nokia gives you certain additional -** rights. These rights are described in the Nokia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU General -** Public License version 3.0 as published by the Free Software Foundation -** and appearing in the file LICENSE.GPL included in the packaging of this -** file. Please review the following information to ensure the GNU General -** Public License version 3.0 requirements will be met: -** http://www.gnu.org/copyleft/gpl.html. -** -** Other Usage -** Alternatively, this file may be used in accordance with the terms and -** conditions contained in a signed written agreement between you and Nokia. -** -** -** -** -** -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "profiledata.h" -#include "constants.h" - -#include -#include -#include -#include -#include - -using namespace Constants; - -QmlEvent::QmlEvent() -{ - eventType = QQmlProfilerService::MaximumRangeType; - eventId = -1; - duration = 0; - calls = 0; - minTime = 0; - maxTime = 0; - timePerCall = 0; - percentOfTime = 0; - medianTime = 0; - isBindingLoop = false; -} - -QmlEvent::~QmlEvent() -{ - qDeleteAll(parentHash.values()); - parentHash.clear(); - qDeleteAll(childrenHash.values()); - childrenHash.clear(); -} - -QmlEvent &QmlEvent::operator=(const QmlEvent &ref) -{ - if (this == &ref) - return *this; - - displayname = ref.displayname; - location = ref.location; - eventHashStr = ref.eventHashStr; - details = ref.details; - eventType = ref.eventType; - duration = ref.duration; - calls = ref.calls; - minTime = ref.minTime; - maxTime = ref.maxTime; - timePerCall = ref.timePerCall; - percentOfTime = ref.percentOfTime; - medianTime = ref.medianTime; - eventId = ref.eventId; - isBindingLoop = ref.isBindingLoop; - - qDeleteAll(parentHash.values()); - parentHash.clear(); - foreach (const QString &key, ref.parentHash.keys()) { - parentHash.insert(key, - new QmlEventSub(ref.parentHash.value(key))); - } - - qDeleteAll(childrenHash.values()); - childrenHash.clear(); - foreach (const QString &key, ref.childrenHash.keys()) { - childrenHash.insert(key, - new QmlEventSub(ref.childrenHash.value(key))); - } - - return *this; -} - -V8Event::V8Event() -{ - line = -1; - eventId = -1; - totalTime = 0; - selfTime = 0; - totalPercent = 0; - selfPercent = 0; -} - -V8Event::~V8Event() -{ - qDeleteAll(parentHash.values()); - parentHash.clear(); - qDeleteAll(childrenHash.values()); - childrenHash.clear(); -} - -V8Event &V8Event::operator=(const V8Event &ref) -{ - if (this == &ref) - return *this; - - displayName = ref.displayName; - filename = ref.filename; - functionName = ref.functionName; - line = ref.line; - totalTime = ref.totalTime; - totalPercent = ref.totalPercent; - selfTime = ref.selfTime; - selfPercent = ref.selfPercent; - eventId = ref.eventId; - - qDeleteAll(parentHash.values()); - parentHash.clear(); - foreach (const QString &key, ref.parentHash.keys()) { - parentHash.insert(key, new V8EventSub(ref.parentHash.value(key))); - } - - qDeleteAll(childrenHash.values()); - childrenHash.clear(); - foreach (const QString &key, ref.childrenHash.keys()) { - childrenHash.insert(key, new V8EventSub(ref.childrenHash.value(key))); - } - return *this; -} - -// endtimedata -struct QmlEventEndTime { - qint64 endTime; - int startTimeIndex; - QmlEvent *description; -}; - -// starttimedata -struct QmlEventStartTime{ - qint64 startTime; - qint64 length; - qint64 level; - int endTimeIndex; - qint64 nestingLevel; - qint64 nestingDepth; - QmlEvent *description; - - // animation-related data - int frameRate; - int animationCount; - - int bindingLoopHead; -}; - -struct QmlEventTypeCount { - QList eventIds; - int nestingCount; -}; - -// used by quicksort -bool compareEndTimes(const QmlEventEndTime &t1, - const QmlEventEndTime &t2) -{ - return t1.endTime < t2.endTime; -} - -bool compareStartTimes(const QmlEventStartTime &t1, - const QmlEventStartTime &t2) -{ - return t1.startTime < t2.startTime; -} - -bool compareStartIndexes(const QmlEventEndTime &t1, - const QmlEventEndTime &t2) -{ - return t1.startTimeIndex < t2.startTimeIndex; -} - -QString qmlEventType(QQmlProfilerService::RangeType typeEnum) -{ - switch (typeEnum) { - case QQmlProfilerService::Painting: - return QLatin1String(TYPE_PAINTING_STR); - break; - case QQmlProfilerService::Compiling: - return QLatin1String(TYPE_COMPILING_STR); - break; - case QQmlProfilerService::Creating: - return QLatin1String(TYPE_CREATING_STR); - break; - case QQmlProfilerService::Binding: - return QLatin1String(TYPE_BINDING_STR); - break; - case QQmlProfilerService::HandlingSignal: - return QLatin1String(TYPE_HANDLINGSIGNAL_STR); - break; - default: - return QString::number((int)typeEnum); - } -} - -QQmlProfilerService::RangeType qmlEventType(const QString &typeString) -{ - if (typeString == QLatin1String(TYPE_PAINTING_STR)) { - return QQmlProfilerService::Painting; - } else if (typeString == QLatin1String(TYPE_COMPILING_STR)) { - return QQmlProfilerService::Compiling; - } else if (typeString == QLatin1String(TYPE_CREATING_STR)) { - return QQmlProfilerService::Creating; - } else if (typeString == QLatin1String(TYPE_BINDING_STR)) { - return QQmlProfilerService::Binding; - } else if (typeString == QLatin1String(TYPE_HANDLINGSIGNAL_STR)) { - return QQmlProfilerService::HandlingSignal; - } else { - bool isNumber = false; - int type = typeString.toUInt(&isNumber); - if (isNumber) { - return (QQmlProfilerService::RangeType)type; - } else { - return QQmlProfilerService::MaximumRangeType; - } - } -} - -QString getHashStringForQmlEvent( - EventLocation location, - QQmlProfilerService::RangeType eventType) -{ - return QString("%1:%2:%3:%4").arg(location.filename, - QString::number(location.line), - QString::number(location.column), - QString::number(eventType)); -} - -class ProfileDataPrivate -{ -public: - - // convenience functions - void clearQmlRootEvent(); - void clearV8RootEvent(); - - // Stored data - QmlEventHash m_eventDescriptions; - QList m_endTimeSortedList; - QList m_startTimeSortedList; - - void collectV8Statistics(); - V8Events m_v8EventList; - QHash m_v8parents; - - QmlEvent m_qmlRootEvent; - V8Event m_v8RootEvent; - QString m_rootEventName; - QString m_rootEventDesc; - - QHash m_typeCounts; - - qint64 m_traceEndTime; - qint64 m_traceStartTime; - qint64 m_qmlMeasuredTime; - qint64 m_v8MeasuredTime; - - QmlEventStartTime *m_lastFrameEvent; - qint64 m_maximumAnimationCount; - qint64 m_minimumAnimationCount; - - // file to load - QString m_filename; -}; - -ProfileData::ProfileData(QObject *parent) : - QObject(parent), - d(new ProfileDataPrivate) -{ - setObjectName("ProfileData"); - - d->m_traceEndTime = 0; - d->m_traceStartTime = -1; - d->m_qmlMeasuredTime = 0; - d->m_v8MeasuredTime = 0; - d->m_rootEventName = tr(""); - d->m_rootEventDesc = tr("Main Program"); - d->clearQmlRootEvent(); - d->clearV8RootEvent(); - d->m_lastFrameEvent = 0; - d->m_maximumAnimationCount = 0; - d->m_minimumAnimationCount = 0; -} - -ProfileData::~ProfileData() -{ - clear(); -} - -void ProfileData::clear() -{ - qDeleteAll(d->m_eventDescriptions.values()); - d->m_eventDescriptions.clear(); - - qDeleteAll(d->m_v8EventList); - d->m_v8EventList.clear(); - - d->m_endTimeSortedList.clear(); - d->m_startTimeSortedList.clear(); - - d->m_v8parents.clear(); - - d->clearQmlRootEvent(); - d->clearV8RootEvent(); - - foreach (QmlEventTypeCount *typeCount, d->m_typeCounts.values()) - delete typeCount; - d->m_typeCounts.clear(); - - d->m_traceEndTime = 0; - d->m_traceStartTime = -1; - d->m_qmlMeasuredTime = 0; - d->m_v8MeasuredTime = 0; - - d->m_lastFrameEvent = 0; - d->m_maximumAnimationCount = 0; - d->m_minimumAnimationCount = 0; - - emit countChanged(); - emit dataClear(); -} - -QmlEvents ProfileData::getQmlEvents() const -{ - return d->m_eventDescriptions.values(); -} - -QmlEvent *ProfileData::qmlEvent(int eventId) const -{ - foreach (QmlEvent *event, d->m_eventDescriptions.values()) { - if (event->eventId == eventId) - return event; - } - return 0; -} - -V8Event *ProfileData::v8Event(int eventId) const -{ - foreach (V8Event *event, d->m_v8EventList) { - if (event->eventId == eventId) - return event; - } - return 0; -} - -const V8Events& ProfileData::getV8Events() const -{ - return d->m_v8EventList; -} - -void ProfileData::addQmlEvent( - QQmlProfilerService::RangeType type, qint64 startTime, qint64 length, - const QStringList &data, const EventLocation &location) -{ - const QChar colon = QLatin1Char(':'); - QString displayName, eventHashStr, details; - EventLocation eventLocation = location; - - emit processingData(); - - // generate details string - if (data.isEmpty()) - details = tr("Source code not available"); - else { - details = data.join(" ").replace('\n'," ").simplified(); - QRegExp rewrite("\\(function \\$(\\w+)\\(\\) \\{ (return |)(.+) \\}\\)"); - bool match = rewrite.exactMatch(details); - if (match) { - details = rewrite.cap(1) + ": " + rewrite.cap(3); - } - if (details.startsWith(QString("file://"))) - details = details.mid(details.lastIndexOf(QChar('/')) + 1); - } - - // backwards compatibility: "compiling" events don't have a proper location in older - // version of the protocol, but the filename is passed in the details string - if (type == QQmlProfilerService::Compiling && eventLocation.filename.isEmpty()) { - eventLocation.filename = details; - eventLocation.line = 1; - eventLocation.column = 1; - } - - // generate hash - if (eventLocation.filename.isEmpty()) { - displayName = tr(""); - eventHashStr = getHashStringForQmlEvent(eventLocation, type); - } else { - const QString filePath = QUrl(eventLocation.filename).path(); - displayName = filePath.mid(filePath.lastIndexOf(QChar('/')) + 1) + colon + QString::number(eventLocation.line); - eventHashStr = getHashStringForQmlEvent(eventLocation, type); - } - - QmlEvent *newEvent; - if (d->m_eventDescriptions.contains(eventHashStr)) { - newEvent = d->m_eventDescriptions[eventHashStr]; - } else { - newEvent = new QmlEvent; - newEvent->displayname = displayName; - newEvent->location = eventLocation; - newEvent->eventHashStr = eventHashStr; - newEvent->eventType = type; - newEvent->details = details; - d->m_eventDescriptions.insert(eventHashStr, newEvent); - } - - QmlEventEndTime endTimeData; - endTimeData.endTime = startTime + length; - endTimeData.description = newEvent; - endTimeData.startTimeIndex = d->m_startTimeSortedList.count(); - - QmlEventStartTime startTimeData; - startTimeData.startTime = startTime; - startTimeData.length = length; - startTimeData.description = newEvent; - startTimeData.endTimeIndex = d->m_endTimeSortedList.count(); - startTimeData.animationCount = -1; - startTimeData.frameRate = 1e9/length; - - d->m_endTimeSortedList << endTimeData; - d->m_startTimeSortedList << startTimeData; - - emit countChanged(); -} - -void ProfileData::addV8Event(int depth, const QString &function, - const QString &filename, int lineNumber, - double totalTime, double selfTime) -{ - QString displayName = filename.mid( - filename.lastIndexOf(QLatin1Char('/')) + 1) + QLatin1Char(':') + - QString::number(lineNumber); - V8Event *eventData = 0; - - // time is given in milliseconds, but internally we store it in microseconds - totalTime *= 1e6; - selfTime *= 1e6; - - // cumulate information - foreach (V8Event *v8event, d->m_v8EventList) { - if (v8event->displayName == displayName && - v8event->functionName == function) { - eventData = v8event; - break; - } - } - - if (!eventData) { - eventData = new V8Event; - eventData->displayName = displayName; - eventData->filename = filename; - eventData->functionName = function; - eventData->line = lineNumber; - eventData->totalTime = totalTime; - eventData->selfTime = selfTime; - d->m_v8EventList << eventData; - } else { - eventData->totalTime += totalTime; - eventData->selfTime += selfTime; - } - d->m_v8parents[depth] = eventData; - - V8Event *parentEvent = 0; - if (depth == 0) { - parentEvent = &d->m_v8RootEvent; - d->m_v8MeasuredTime += totalTime; - } - if (depth > 0 && d->m_v8parents.contains(depth-1)) { - parentEvent = d->m_v8parents.value(depth-1); - } - - if (parentEvent != 0) { - if (!eventData->parentHash.contains(parentEvent->displayName)) { - V8EventSub *newParentSub = new V8EventSub(parentEvent); - newParentSub->totalTime = totalTime; - - eventData->parentHash.insert(parentEvent->displayName, newParentSub ); - } else { - V8EventSub *newParentSub = - eventData->parentHash.value(parentEvent->displayName); - newParentSub->totalTime += totalTime; - } - - if (!parentEvent->childrenHash.contains(eventData->displayName)) { - V8EventSub *newChildSub = new V8EventSub(eventData); - newChildSub->totalTime = totalTime; - - parentEvent->childrenHash.insert(eventData->displayName, newChildSub); - } else { - V8EventSub *newChildSub = - parentEvent->childrenHash.value(eventData->displayName); - newChildSub->totalTime += totalTime; - } - } -} - -void ProfileData::addFrameEvent(qint64 time, int framerate, int animationcount) -{ - QString displayName, eventHashStr, details; - - emit processingData(); - - details = tr("Animation Timer Update"); - displayName = tr(""); - eventHashStr = displayName; - - QmlEvent *newEvent; - if (d->m_eventDescriptions.contains(eventHashStr)) { - newEvent = d->m_eventDescriptions[eventHashStr]; - } else { - newEvent = new QmlEvent; - newEvent->displayname = displayName; - newEvent->eventHashStr = eventHashStr; - newEvent->eventType = QQmlProfilerService::Painting; - newEvent->details = details; - d->m_eventDescriptions.insert(eventHashStr, newEvent); - } - - qint64 length = 1e9/framerate; - // avoid overlap - if (d->m_lastFrameEvent && - d->m_lastFrameEvent->startTime + d->m_lastFrameEvent->length >= time) { - d->m_lastFrameEvent->length = time - 1 - d->m_lastFrameEvent->startTime; - d->m_endTimeSortedList[d->m_lastFrameEvent->endTimeIndex].endTime = - d->m_lastFrameEvent->startTime + d->m_lastFrameEvent->length; - } - - QmlEventEndTime endTimeData; - endTimeData.endTime = time + length; - endTimeData.description = newEvent; - endTimeData.startTimeIndex = d->m_startTimeSortedList.count(); - - QmlEventStartTime startTimeData; - startTimeData.startTime = time; - startTimeData.length = length; - startTimeData.description = newEvent; - startTimeData.endTimeIndex = d->m_endTimeSortedList.count(); - startTimeData.animationCount = animationcount; - startTimeData.frameRate = framerate; - - d->m_endTimeSortedList << endTimeData; - d->m_startTimeSortedList << startTimeData; - - d->m_lastFrameEvent = &d->m_startTimeSortedList.last(); - - emit countChanged(); -} - -void ProfileDataPrivate::collectV8Statistics() -{ - if (!m_v8EventList.isEmpty()) { - double totalTimes = m_v8MeasuredTime; - double selfTimes = 0; - foreach (V8Event *v8event, m_v8EventList) { - selfTimes += v8event->selfTime; - } - - // prevent divisions by 0 - if (totalTimes == 0) - totalTimes = 1; - if (selfTimes == 0) - selfTimes = 1; - - // insert root event in eventlist - // the +1 ns is to get it on top of the sorted list - m_v8RootEvent.totalTime = m_v8MeasuredTime + 1; - m_v8RootEvent.selfTime = 0; - - int rootEventIndex = -1; - for (int ndx = 0; ndx < m_v8EventList.count(); ndx++) - { - if (m_v8EventList.at(ndx)->displayName == m_rootEventName) { - m_v8RootEvent = *m_v8EventList.at(ndx); - rootEventIndex = ndx; - break; - } - } - if (rootEventIndex == -1) { - rootEventIndex = m_v8EventList.count(); - V8Event *newRootEvent = new V8Event; - *newRootEvent = m_v8RootEvent; - m_v8EventList << newRootEvent; - } - - foreach (V8Event *v8event, m_v8EventList) { - v8event->totalPercent = v8event->totalTime * 100.0 / totalTimes; - v8event->selfPercent = v8event->selfTime * 100.0 / selfTimes; - } - - int index = 0; - foreach (V8Event *v8event, m_v8EventList) { - v8event->eventId = index++; - } - m_v8RootEvent.eventId = m_v8EventList[rootEventIndex]->eventId; - } -} - -void ProfileData::setTraceEndTime( qint64 time ) -{ - d->m_traceEndTime = time; -} - -void ProfileData::setTraceStartTime( qint64 time ) -{ - d->m_traceStartTime = time; -} - -void ProfileData::complete() -{ - emit postProcessing(); - d->collectV8Statistics(); - postProcess(); -} - -void ProfileDataPrivate::clearQmlRootEvent() -{ - m_qmlRootEvent.displayname = m_rootEventName; - m_qmlRootEvent.location = EventLocation(); - m_qmlRootEvent.eventHashStr = m_rootEventName; - m_qmlRootEvent.details = m_rootEventDesc; - m_qmlRootEvent.eventType = QQmlProfilerService::Binding; - m_qmlRootEvent.duration = 0; - m_qmlRootEvent.calls = 0; - m_qmlRootEvent.minTime = 0; - m_qmlRootEvent.maxTime = 0; - m_qmlRootEvent.timePerCall = 0; - m_qmlRootEvent.percentOfTime = 0; - m_qmlRootEvent.medianTime = 0; - m_qmlRootEvent.eventId = -1; - - qDeleteAll(m_qmlRootEvent.parentHash.values()); - qDeleteAll(m_qmlRootEvent.childrenHash.values()); - m_qmlRootEvent.parentHash.clear(); - m_qmlRootEvent.childrenHash.clear(); -} - -void ProfileDataPrivate::clearV8RootEvent() -{ - m_v8RootEvent.displayName = m_rootEventName; - m_v8RootEvent.functionName = m_rootEventDesc; - m_v8RootEvent.line = -1; - m_v8RootEvent.totalTime = 0; - m_v8RootEvent.totalPercent = 0; - m_v8RootEvent.selfTime = 0; - m_v8RootEvent.selfPercent = 0; - m_v8RootEvent.eventId = -1; - - qDeleteAll(m_v8RootEvent.parentHash.values()); - qDeleteAll(m_v8RootEvent.childrenHash.values()); - m_v8RootEvent.parentHash.clear(); - m_v8RootEvent.childrenHash.clear(); -} - -void ProfileData::compileStatistics(qint64 startTime, qint64 endTime) -{ - int index; - int fromIndex = findFirstIndex(startTime); - int toIndex = findLastIndex(endTime); - double totalTime = 0; - - // clear existing statistics - foreach (QmlEvent *eventDescription, - d->m_eventDescriptions.values()) { - eventDescription->calls = 0; - // maximum possible value - eventDescription->minTime = d->m_endTimeSortedList.last().endTime; - eventDescription->maxTime = 0; - eventDescription->medianTime = 0; - eventDescription->duration = 0; - qDeleteAll(eventDescription->parentHash); - qDeleteAll(eventDescription->childrenHash); - eventDescription->parentHash.clear(); - eventDescription->childrenHash.clear(); - } - - // create root event for statistics - d->clearQmlRootEvent(); - - // compute parent-child relationship and call count - QHash lastParent; - for (index = fromIndex; index <= toIndex; index++) { - QmlEvent *eventDescription = - d->m_startTimeSortedList[index].description; - - if (d->m_startTimeSortedList[index].startTime > endTime || - d->m_startTimeSortedList[index].startTime + - d->m_startTimeSortedList[index].length < startTime) { - continue; - } - - if (eventDescription->eventType == QQmlProfilerService::Painting) { - // skip animation/paint events - continue; - } - - eventDescription->calls++; - qint64 duration = d->m_startTimeSortedList[index].length; - eventDescription->duration += duration; - if (eventDescription->maxTime < duration) - eventDescription->maxTime = duration; - if (eventDescription->minTime > duration) - eventDescription->minTime = duration; - - int level = d->m_startTimeSortedList[index].level; - - QmlEvent *parentEvent = &d->m_qmlRootEvent; - if (level > MIN_LEVEL && lastParent.contains(level-1)) { - parentEvent = lastParent[level-1]; - } - - if (!eventDescription->parentHash.contains(parentEvent->eventHashStr)) { - QmlEventSub *newParentEvent = - new QmlEventSub(parentEvent); - newParentEvent->calls = 1; - newParentEvent->duration = duration; - - eventDescription->parentHash.insert(parentEvent->eventHashStr, - newParentEvent); - } else { - QmlEventSub *newParentEvent = - eventDescription->parentHash.value(parentEvent->eventHashStr); - newParentEvent->duration += duration; - newParentEvent->calls++; - } - - if (!parentEvent->childrenHash.contains(eventDescription->eventHashStr)) { - QmlEventSub *newChildEvent = - new QmlEventSub(eventDescription); - newChildEvent->calls = 1; - newChildEvent->duration = duration; - - parentEvent->childrenHash.insert(eventDescription->eventHashStr, - newChildEvent); - } else { - QmlEventSub *newChildEvent = - parentEvent->childrenHash.value(eventDescription->eventHashStr); - newChildEvent->duration += duration; - newChildEvent->calls++; - } - - lastParent[level] = eventDescription; - - if (level == MIN_LEVEL) { - totalTime += duration; - } - } - - // fake rootEvent statistics - // the +1 nanosecond is to force it to be on top of the sorted list - d->m_qmlRootEvent.duration = totalTime+1; - d->m_qmlRootEvent.minTime = totalTime+1; - d->m_qmlRootEvent.maxTime = totalTime+1; - d->m_qmlRootEvent.medianTime = totalTime+1; - if (totalTime > 0) - d->m_qmlRootEvent.calls = 1; - - // insert into list - QmlEvent *listedRootEvent = - d->m_eventDescriptions.value(d->m_rootEventName); - if (!listedRootEvent) { - listedRootEvent = new QmlEvent; - d->m_eventDescriptions.insert(d->m_rootEventName, listedRootEvent); - } - *listedRootEvent = d->m_qmlRootEvent; - - // compute percentages - foreach (QmlEvent *binding, d->m_eventDescriptions.values()) { - binding->percentOfTime = binding->duration * 100.0 / totalTime; - binding->timePerCall = binding->calls > 0 ? - double(binding->duration) / binding->calls : 0; - } - - // compute median time - QHash < QmlEvent* , QList > durationLists; - for (index = fromIndex; index <= toIndex; index++) { - QmlEvent *desc = d->m_startTimeSortedList[index].description; - qint64 len = d->m_startTimeSortedList[index].length; - durationLists[desc].append(len); - } - QMutableHashIterator < QmlEvent* , QList > iter(durationLists); - while (iter.hasNext()) { - iter.next(); - if (!iter.value().isEmpty()) { - qSort(iter.value()); - iter.key()->medianTime = iter.value().at(iter.value().count()/2); - } - } -} - -void ProfileData::prepareForDisplay() -{ - // generate numeric ids - int ndx = 0; - foreach (QmlEvent *binding, d->m_eventDescriptions.values()) { - binding->eventId = ndx++; - } - - // collect type counts - foreach (const QmlEventStartTime &eventStartData, - d->m_startTimeSortedList) { - int typeNumber = eventStartData.description->eventType; - if (!d->m_typeCounts.contains(typeNumber)) { - d->m_typeCounts[typeNumber] = new QmlEventTypeCount; - d->m_typeCounts[typeNumber]->nestingCount = 0; - } - if (eventStartData.nestingLevel > - d->m_typeCounts[typeNumber]->nestingCount) { - d->m_typeCounts[typeNumber]->nestingCount = eventStartData.nestingLevel; - } - if (!d->m_typeCounts[typeNumber]->eventIds.contains( - eventStartData.description->eventId)) - d->m_typeCounts[typeNumber]->eventIds << eventStartData.description->eventId; - } -} - -void ProfileData::sortStartTimes() -{ - if (d->m_startTimeSortedList.count() < 2) - return; - - // assuming startTimes is partially sorted - // identify blocks of events and sort them with quicksort - QList::iterator itFrom = - d->m_startTimeSortedList.end() - 2; - QList::iterator itTo = - d->m_startTimeSortedList.end() - 1; - - while (itFrom != d->m_startTimeSortedList.begin() && - itTo != d->m_startTimeSortedList.begin()) { - // find block to sort - while ( itFrom != d->m_startTimeSortedList.begin() - && itTo->startTime > itFrom->startTime ) { - itTo--; - itFrom = itTo - 1; - } - - // if we're at the end of the list - if (itFrom == d->m_startTimeSortedList.begin()) - break; - - // find block length - while ( itFrom != d->m_startTimeSortedList.begin() - && itTo->startTime <= itFrom->startTime ) - itFrom--; - - if (itTo->startTime <= itFrom->startTime) - qSort(itFrom, itTo + 1, compareStartTimes); - else - qSort(itFrom + 1, itTo + 1, compareStartTimes); - - // move to next block - itTo = itFrom; - itFrom = itTo - 1; - } - - // link back the endTimes - for (int i = 0; i < d->m_startTimeSortedList.length(); i++) - d->m_endTimeSortedList[d->m_startTimeSortedList[i].endTimeIndex].startTimeIndex = i; -} - -void ProfileData::sortEndTimes() -{ - // assuming endTimes is partially sorted - // identify blocks of events and sort them with quicksort - - if (d->m_endTimeSortedList.count() < 2) - return; - - QList::iterator itFrom = - d->m_endTimeSortedList.begin(); - QList::iterator itTo = - d->m_endTimeSortedList.begin() + 1; - - while (itTo != d->m_endTimeSortedList.end() && - itFrom != d->m_endTimeSortedList.end()) { - // find block to sort - while ( itTo != d->m_endTimeSortedList.end() - && d->m_startTimeSortedList[itTo->startTimeIndex].startTime > - d->m_startTimeSortedList[itFrom->startTimeIndex].startTime + - d->m_startTimeSortedList[itFrom->startTimeIndex].length ) { - itFrom++; - itTo = itFrom+1; - } - - // if we're at the end of the list - if (itTo == d->m_endTimeSortedList.end()) - break; - - // find block length - while ( itTo != d->m_endTimeSortedList.end() - && d->m_startTimeSortedList[itTo->startTimeIndex].startTime <= - d->m_startTimeSortedList[itFrom->startTimeIndex].startTime + - d->m_startTimeSortedList[itFrom->startTimeIndex].length ) - itTo++; - - // sort block - qSort(itFrom, itTo, compareEndTimes); - - // move to next block - itFrom = itTo; - itTo = itFrom+1; - - } - - // link back the startTimes - for (int i = 0; i < d->m_endTimeSortedList.length(); i++) - d->m_startTimeSortedList[d->m_endTimeSortedList[i].startTimeIndex].endTimeIndex = i; -} - -void ProfileData::findAnimationLimits() -{ - d->m_maximumAnimationCount = 0; - d->m_minimumAnimationCount = 0; - d->m_lastFrameEvent = 0; - - for (int i = 0; i < d->m_startTimeSortedList.count(); i++) { - if (d->m_startTimeSortedList[i].description->eventType == - QQmlProfilerService::Painting && - d->m_startTimeSortedList[i].animationCount >= 0) { - int animationcount = d->m_startTimeSortedList[i].animationCount; - if (d->m_lastFrameEvent) { - if (animationcount > d->m_maximumAnimationCount) - d->m_maximumAnimationCount = animationcount; - if (animationcount < d->m_minimumAnimationCount) - d->m_minimumAnimationCount = animationcount; - } else { - d->m_maximumAnimationCount = animationcount; - d->m_minimumAnimationCount = animationcount; - } - d->m_lastFrameEvent = &d->m_startTimeSortedList[i]; - } - } -} - -void ProfileData::computeNestingLevels() -{ - // compute levels - QHash endtimesPerLevel; - QList nestingLevels; - QList < QHash > endtimesPerNestingLevel; - int level = MIN_LEVEL; - endtimesPerLevel[MIN_LEVEL] = 0; - - for (int i = 0; i < QQmlProfilerService::MaximumRangeType; i++) { - nestingLevels << MIN_LEVEL; - QHash dummyHash; - dummyHash[MIN_LEVEL] = 0; - endtimesPerNestingLevel << dummyHash; - } - - for (int i=0; im_startTimeSortedList.count(); i++) { - qint64 st = d->m_startTimeSortedList[i].startTime; - int type = d->m_startTimeSortedList[i].description->eventType; - - if (type == QQmlProfilerService::Painting) { - // animation/paint events have level 1 by definition, - // but are not considered parents of other events for - // statistical purposes - d->m_startTimeSortedList[i].level = MIN_LEVEL; - d->m_startTimeSortedList[i].nestingLevel = MIN_LEVEL; - continue; - } - - // general level - if (endtimesPerLevel[level] > st) { - level++; - } else { - while (level > MIN_LEVEL && endtimesPerLevel[level-1] <= st) - level--; - } - endtimesPerLevel[level] = st + d->m_startTimeSortedList[i].length; - - // per type - if (endtimesPerNestingLevel[type][nestingLevels[type]] > st) { - nestingLevels[type]++; - } else { - while (nestingLevels[type] > MIN_LEVEL && - endtimesPerNestingLevel[type][nestingLevels[type]-1] <= st) - nestingLevels[type]--; - } - endtimesPerNestingLevel[type][nestingLevels[type]] = st + - d->m_startTimeSortedList[i].length; - - d->m_startTimeSortedList[i].level = level; - d->m_startTimeSortedList[i].nestingLevel = nestingLevels[type]; - - if (level == MIN_LEVEL) { - d->m_qmlMeasuredTime += d->m_startTimeSortedList[i].length; - } - } -} - -void ProfileData::computeNestingDepth() -{ - QHash nestingDepth; - for (int i = 0; i < d->m_endTimeSortedList.count(); i++) { - int type = d->m_endTimeSortedList[i].description->eventType; - int nestingInType = d->m_startTimeSortedList[d->m_endTimeSortedList[i].startTimeIndex].nestingLevel; - if (!nestingDepth.contains(type)) - nestingDepth[type] = nestingInType; - else { - int nd = nestingDepth[type]; - nestingDepth[type] = nd > nestingInType ? nd : nestingInType; - } - - d->m_startTimeSortedList[d->m_endTimeSortedList[i].startTimeIndex].nestingDepth - = nestingDepth[type]; - if (nestingInType == MIN_LEVEL) - nestingDepth[type] = MIN_LEVEL; - } -} - -void ProfileData::postProcess() -{ - if (count() != 0) { - sortStartTimes(); - sortEndTimes(); - findAnimationLimits(); - computeLevels(); - linkEndsToStarts(); - reloadDetails(); - compileStatistics(traceStartTime(), traceEndTime()); - prepareForDisplay(); - } - // data is ready even when there's no data - emit dataReady(); -} - -void ProfileData::linkEndsToStarts() -{ - for (int i = 0; i < d->m_startTimeSortedList.count(); i++) - d->m_endTimeSortedList[d->m_startTimeSortedList[i].endTimeIndex].startTimeIndex = i; -} - -void ProfileData::computeLevels() -{ - computeNestingLevels(); - computeNestingDepth(); -} - -void ProfileData::reloadDetails() -{ - // request binding/signal details from the AST - foreach (QmlEvent *event, d->m_eventDescriptions.values()) { - if (event->eventType != QQmlProfilerService::Binding && - event->eventType != QQmlProfilerService::HandlingSignal) - continue; - - // This skips anonymous bindings in Qt4.8 (we don't have valid location data for them) - if (event->location.filename.isEmpty()) - continue; - - // Skip non-anonymous bindings from Qt4.8 (we already have correct details for them) - if (event->location.column == -1) - continue; - - emit requestDetailsForLocation(event->eventType, event->location); - } - emit reloadDocumentsForDetails(); -} - -void ProfileData::findBindingLoops(qint64 startTime, qint64 endTime) -{ - // first clear existing data - foreach (QmlEvent *event, d->m_eventDescriptions.values()) { - event->isBindingLoop = false; - foreach (QmlEventSub *parentEvent, event->parentHash.values()) - parentEvent->inLoopPath = false; - foreach (QmlEventSub *childEvent, event->childrenHash.values()) - childEvent->inLoopPath = false; - } - - QList stackRefs; - QList stack; - int fromIndex = findFirstIndex(startTime); - int toIndex = findLastIndex(endTime); - - for (int i = 0; i < d->m_startTimeSortedList.count(); i++) { - QmlEvent *currentEvent = d->m_startTimeSortedList[i].description; - QmlEventStartTime *inTimeEvent = &d->m_startTimeSortedList[i]; - inTimeEvent->bindingLoopHead = -1; - - // managing call stack - for (int j = stack.count() - 1; j >= 0; j--) { - if (stack[j]->startTime + stack[j]->length <= inTimeEvent->startTime) { - stack.removeAt(j); - stackRefs.removeAt(j); - } - } - - bool loopDetected = stackRefs.contains(currentEvent); - stack << inTimeEvent; - stackRefs << currentEvent; - - if (loopDetected) { - if (i >= fromIndex && i <= toIndex) { - // for the statistics - currentEvent->isBindingLoop = true; - for (int j = stackRefs.indexOf(currentEvent); j < stackRefs.count()-1; j++) { - QmlEventSub *nextEventSub = stackRefs[j]->childrenHash.value(stackRefs[j+1]->eventHashStr); - nextEventSub->inLoopPath = true; - QmlEventSub *prevEventSub = stackRefs[j+1]->parentHash.value(stackRefs[j]->eventHashStr); - prevEventSub->inLoopPath = true; - } - } - - // use crossed references to find index in starttimesortedlist - QmlEventStartTime *head = stack[stackRefs.indexOf(currentEvent)]; - inTimeEvent->bindingLoopHead = d->m_endTimeSortedList[head->endTimeIndex].startTimeIndex; - d->m_startTimeSortedList[inTimeEvent->bindingLoopHead].bindingLoopHead = i; - } - } -} - -void ProfileData::rewriteDetailsString( - QQmlProfilerService::RangeType eventType, - const EventLocation &location, const QString &newString) -{ - QString eventHashStr = getHashStringForQmlEvent(location, - eventType); - Q_ASSERT(d->m_eventDescriptions.contains(eventHashStr)); - d->m_eventDescriptions.value(eventHashStr)->details = newString; - emit detailsChanged(d->m_eventDescriptions.value(eventHashStr)->eventId, - newString); -} - -void ProfileData::finishedRewritingDetails() -{ - emit reloadDetailLabels(); -} - -// get list of events between A and B: -// find fist event with endtime after A -> aa -// find last event with starttime before B -> bb -// list is from parent of aa with level=0 to bb, in the "sorted by starttime" list -int ProfileData::findFirstIndex(qint64 startTime) const -{ - int candidate = -1; - // in the "endtime" list, find the first event that ends after startTime - if (d->m_endTimeSortedList.isEmpty()) - return 0; // -1 - if (d->m_endTimeSortedList.length() == 1 || - d->m_endTimeSortedList.first().endTime >= startTime) - candidate = 0; - else - if (d->m_endTimeSortedList.last().endTime <= startTime) - return 0; // -1 - - if (candidate == -1) - { - int fromIndex = 0; - int toIndex = d->m_endTimeSortedList.count()-1; - while (toIndex - fromIndex > 1) { - int midIndex = (fromIndex + toIndex)/2; - if (d->m_endTimeSortedList[midIndex].endTime < startTime) - fromIndex = midIndex; - else - toIndex = midIndex; - } - - candidate = toIndex; - } - - int ndx = d->m_endTimeSortedList[candidate].startTimeIndex; - - // and then go to the parent - while (d->m_startTimeSortedList[ndx].level != MIN_LEVEL && ndx > 0) - ndx--; - - return ndx; -} - -int ProfileData::findFirstIndexNoParents(qint64 startTime) const -{ - int candidate = -1; - // in the "endtime" list, find the first event that ends after startTime - if (d->m_endTimeSortedList.isEmpty()) - return 0; // -1 - if (d->m_endTimeSortedList.length() == 1 || - d->m_endTimeSortedList.first().endTime >= startTime) - candidate = 0; - else - if (d->m_endTimeSortedList.last().endTime <= startTime) - return 0; // -1 - - if (candidate == -1) { - int fromIndex = 0; - int toIndex = d->m_endTimeSortedList.count()-1; - while (toIndex - fromIndex > 1) { - int midIndex = (fromIndex + toIndex)/2; - if (d->m_endTimeSortedList[midIndex].endTime < startTime) - fromIndex = midIndex; - else - toIndex = midIndex; - } - - candidate = toIndex; - } - - int ndx = d->m_endTimeSortedList[candidate].startTimeIndex; - - return ndx; -} - -int ProfileData::findLastIndex(qint64 endTime) const -{ - // in the "starttime" list, find the last event that starts before endtime - if (d->m_startTimeSortedList.isEmpty()) - return 0; // -1 - if (d->m_startTimeSortedList.first().startTime >= endTime) - return 0; // -1 - if (d->m_startTimeSortedList.length() == 1) - return 0; - if (d->m_startTimeSortedList.last().startTime <= endTime) - return d->m_startTimeSortedList.count()-1; - - int fromIndex = 0; - int toIndex = d->m_startTimeSortedList.count()-1; - while (toIndex - fromIndex > 1) { - int midIndex = (fromIndex + toIndex)/2; - if (d->m_startTimeSortedList[midIndex].startTime < endTime) - fromIndex = midIndex; - else - toIndex = midIndex; - } - - return fromIndex; -} - -qint64 ProfileData::firstTimeMark() const -{ - if (d->m_startTimeSortedList.isEmpty()) - return 0; - else { - return d->m_startTimeSortedList[0].startTime; - } -} - -qint64 ProfileData::lastTimeMark() const -{ - if (d->m_endTimeSortedList.isEmpty()) - return 0; - else { - return d->m_endTimeSortedList.last().endTime; - } -} - -qint64 ProfileData::traceStartTime() const -{ - return d->m_traceStartTime != -1? d->m_traceStartTime : firstTimeMark(); -} - -qint64 ProfileData::traceEndTime() const -{ - return d->m_traceEndTime ? d->m_traceEndTime : lastTimeMark(); -} - -qint64 ProfileData::traceDuration() const -{ - return traceEndTime() - traceStartTime(); -} - -qint64 ProfileData::qmlMeasuredTime() const -{ - return d->m_qmlMeasuredTime; -} -qint64 ProfileData::v8MeasuredTime() const -{ - return d->m_v8MeasuredTime; -} - -int ProfileData::count() const -{ - return d->m_startTimeSortedList.count(); -} - -//////////////////////////////////////////////////////////////////////////////// - - -bool ProfileData::save(const QString &filename) -{ - if (count() == 0) { - emit error(tr("No data to save")); - return false; - } - - QFile file(filename); - if (!file.open(QIODevice::WriteOnly)) { - emit error(tr("Could not open %1 for writing").arg(filename)); - return false; - } - - QXmlStreamWriter stream(&file); - stream.setAutoFormatting(true); - stream.writeStartDocument(); - - stream.writeStartElement("trace"); - stream.writeAttribute("version", PROFILER_FILE_VERSION); - - stream.writeAttribute("traceStart", QString::number(traceStartTime())); - stream.writeAttribute("traceEnd", QString::number(traceEndTime())); - - stream.writeStartElement("eventData"); - stream.writeAttribute("totalTime", QString::number(d->m_qmlMeasuredTime)); - - foreach (const QmlEvent *eventData, d->m_eventDescriptions.values()) { - stream.writeStartElement("event"); - stream.writeAttribute("index", - QString::number( - d->m_eventDescriptions.keys().indexOf( - eventData->eventHashStr))); - stream.writeTextElement("displayname", eventData->displayname); - stream.writeTextElement("type", qmlEventType(eventData->eventType)); - if (!eventData->location.filename.isEmpty()) { - stream.writeTextElement("filename", eventData->location.filename); - stream.writeTextElement("line", - QString::number(eventData->location.line)); - stream.writeTextElement("column", - QString::number(eventData->location.column)); - } - stream.writeTextElement("details", eventData->details); - stream.writeEndElement(); - } - stream.writeEndElement(); // eventData - - stream.writeStartElement("eventList"); - foreach (const QmlEventStartTime &rangedEvent, - d->m_startTimeSortedList) { - stream.writeStartElement("range"); - stream.writeAttribute("startTime", QString::number(rangedEvent.startTime)); - stream.writeAttribute("duration", QString::number(rangedEvent.length)); - stream.writeAttribute("eventIndex", - QString::number(d->m_eventDescriptions.keys().indexOf( - rangedEvent.description->eventHashStr))); - if (rangedEvent.description->eventType == - QQmlProfilerService::Painting && rangedEvent.animationCount >= 0) { - // animation frame - stream.writeAttribute("framerate", - QString::number(rangedEvent.frameRate)); - stream.writeAttribute("animationcount", - QString::number(rangedEvent.animationCount)); - } - stream.writeEndElement(); - } - stream.writeEndElement(); // eventList - - stream.writeStartElement("v8profile"); // v8 profiler output - stream.writeAttribute("totalTime", QString::number(d->m_v8MeasuredTime)); - foreach (V8Event *v8event, d->m_v8EventList) { - stream.writeStartElement("event"); - stream.writeAttribute("index", - QString::number(d->m_v8EventList.indexOf(v8event))); - stream.writeTextElement("displayname", v8event->displayName); - stream.writeTextElement("functionname", v8event->functionName); - if (!v8event->filename.isEmpty()) { - stream.writeTextElement("filename", v8event->filename); - stream.writeTextElement("line", QString::number(v8event->line)); - } - stream.writeTextElement("totalTime", - QString::number(v8event->totalTime)); - stream.writeTextElement("selfTime", QString::number(v8event->selfTime)); - if (!v8event->childrenHash.isEmpty()) { - stream.writeStartElement("childrenEvents"); - QStringList childrenIndexes; - QStringList childrenTimes; - QStringList parentTimes; - foreach (V8EventSub *v8child, v8event->childrenHash.values()) { - childrenIndexes << QString::number(v8child->reference->eventId); - childrenTimes << QString::number(v8child->totalTime); - parentTimes << QString::number( - d->m_v8EventList[v8child->reference->eventId]-> - parentHash[v8event->displayName]->totalTime); - } - - stream.writeAttribute("list", childrenIndexes.join(QString(", "))); - stream.writeAttribute("childrenTimes", - childrenTimes.join(QString(", "))); - stream.writeAttribute("parentTimes", - parentTimes.join(QString(", "))); - stream.writeEndElement(); - } - stream.writeEndElement(); - } - stream.writeEndElement(); // v8 profiler output - - stream.writeEndElement(); // trace - stream.writeEndDocument(); - - file.close(); - return true; -} - -void ProfileData::setFilename(const QString &filename) -{ - d->m_filename = filename; -} - -void ProfileData::load(const QString &filename) -{ - setFilename(filename); - load(); -} - -// "be strict in your output but tolerant in your inputs" -void ProfileData::load() -{ - QString filename = d->m_filename; - - QFile file(filename); - - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - emit error(tr("Could not open %1 for reading").arg(filename)); - return; - } - - emit processingData(); - - // erase current - clear(); - - bool readingQmlEvents = false; - bool readingV8Events = false; - QHash descriptionBuffer; - QmlEvent *currentEvent = 0; - QHash v8eventBuffer; - QHash childrenIndexes; - QHash childrenTimes; - QHash parentTimes; - V8Event *v8event = 0; - bool startTimesAreSorted = true; - bool validVersion = true; - - // time computation - d->m_v8MeasuredTime = 0; - d->m_qmlMeasuredTime = 0; - double cumulatedV8Time = 0; - - QXmlStreamReader stream(&file); - - while (validVersion && !stream.atEnd() && !stream.hasError()) { - QXmlStreamReader::TokenType token = stream.readNext(); - QString elementName = stream.name().toString(); - switch (token) { - case QXmlStreamReader::StartDocument : continue; - case QXmlStreamReader::StartElement : { - if (elementName == "trace") { - QXmlStreamAttributes attributes = stream.attributes(); - if (attributes.hasAttribute("version")) - validVersion = - attributes.value("version").toString() == - PROFILER_FILE_VERSION; - else - validVersion = false; - if (attributes.hasAttribute("traceStart")) - setTraceStartTime(attributes.value("traceStart"). - toString().toLongLong()); - if (attributes.hasAttribute("traceEnd")) - setTraceEndTime(attributes.value("traceEnd"). - toString().toLongLong()); - } - if (elementName == "eventData" && !readingV8Events) { - readingQmlEvents = true; - QXmlStreamAttributes attributes = stream.attributes(); - if (attributes.hasAttribute("totalTime")) - d->m_qmlMeasuredTime = attributes.value("totalTime"). - toString().toDouble(); - break; - } - if (elementName == "v8profile" && !readingQmlEvents) { - readingV8Events = true; - QXmlStreamAttributes attributes = stream.attributes(); - if (attributes.hasAttribute("totalTime")) - d->m_v8MeasuredTime = attributes.value("totalTime"). - toString().toDouble(); - break; - } - - if (elementName == "trace") { - QXmlStreamAttributes attributes = stream.attributes(); - if (attributes.hasAttribute("traceStart")) - setTraceStartTime(attributes.value("traceStart"). - toString().toLongLong()); - if (attributes.hasAttribute("traceEnd")) - setTraceEndTime(attributes.value("traceEnd"). - toString().toLongLong()); - } - - if (elementName == "range") { - QmlEventStartTime rangedEvent; - QXmlStreamAttributes attributes = stream.attributes(); - if (attributes.hasAttribute("startTime")) - rangedEvent.startTime = attributes.value("startTime"). - toString().toLongLong(); - if (attributes.hasAttribute("duration")) - rangedEvent.length = attributes.value("duration"). - toString().toLongLong(); - if (attributes.hasAttribute("framerate")) - rangedEvent.frameRate = attributes.value("framerate"). - toString().toInt(); - if (attributes.hasAttribute("animationcount")) - rangedEvent.animationCount = attributes.value("animationcount"). - toString().toInt(); - else - rangedEvent.animationCount = -1; - if (attributes.hasAttribute("eventIndex")) { - int ndx = attributes.value("eventIndex").toString().toInt(); - if (!descriptionBuffer.value(ndx)) - descriptionBuffer[ndx] = new QmlEvent; - rangedEvent.description = descriptionBuffer.value(ndx); - } - rangedEvent.endTimeIndex = d->m_endTimeSortedList.length(); - - if (!d->m_startTimeSortedList.isEmpty() - && rangedEvent.startTime < - d->m_startTimeSortedList.last().startTime) - startTimesAreSorted = false; - d->m_startTimeSortedList << rangedEvent; - - QmlEventEndTime endTimeEvent; - endTimeEvent.endTime = rangedEvent.startTime + rangedEvent.length; - endTimeEvent.startTimeIndex = d->m_startTimeSortedList.length()-1; - endTimeEvent.description = rangedEvent.description; - d->m_endTimeSortedList << endTimeEvent; - break; - } - - if (readingQmlEvents) { - if (elementName == "event") { - QXmlStreamAttributes attributes = stream.attributes(); - if (attributes.hasAttribute("index")) { - int ndx = attributes.value("index").toString().toInt(); - if (!descriptionBuffer.value(ndx)) - descriptionBuffer[ndx] = new QmlEvent; - currentEvent = descriptionBuffer[ndx]; - } else { - currentEvent = 0; - } - break; - } - - // the remaining are eventdata or v8eventdata elements - if (!currentEvent) - break; - - stream.readNext(); - if (stream.tokenType() != QXmlStreamReader::Characters) - break; - QString readData = stream.text().toString(); - - if (elementName == "displayname") { - currentEvent->displayname = readData; - break; - } - if (elementName == "type") { - currentEvent->eventType = qmlEventType(readData); - break; - } - if (elementName == "filename") { - currentEvent->location.filename = readData; - break; - } - if (elementName == "line") { - currentEvent->location.line = readData.toInt(); - break; - } - if (elementName == "column") { - currentEvent->location.column = readData.toInt(); - } - if (elementName == "details") { - currentEvent->details = readData; - break; - } - } - - if (readingV8Events) { - if (elementName == "event") { - QXmlStreamAttributes attributes = stream.attributes(); - if (attributes.hasAttribute("index")) { - int ndx = attributes.value("index").toString().toInt(); - if (!v8eventBuffer.value(ndx)) - v8eventBuffer[ndx] = new V8Event; - v8event = v8eventBuffer[ndx]; - } else { - v8event = 0; - } - break; - } - - // the remaining are eventdata or v8eventdata elements - if (!v8event) - break; - - if (elementName == "childrenEvents") { - QXmlStreamAttributes attributes = stream.attributes(); - int eventIndex = v8eventBuffer.key(v8event); - if (attributes.hasAttribute("list")) { - // store for later parsing (we haven't read all the events yet) - childrenIndexes[eventIndex] = - attributes.value("list").toString(); - } - if (attributes.hasAttribute("childrenTimes")) { - childrenTimes[eventIndex] = - attributes.value("childrenTimes").toString(); - } - if (attributes.hasAttribute("parentTimes")) { - parentTimes[eventIndex] = - attributes.value("parentTimes").toString(); - } - } - - stream.readNext(); - if (stream.tokenType() != QXmlStreamReader::Characters) - break; - QString readData = stream.text().toString(); - - if (elementName == "displayname") { - v8event->displayName = readData; - break; - } - - if (elementName == "functionname") { - v8event->functionName = readData; - break; - } - - if (elementName == "filename") { - v8event->filename = readData; - break; - } - - if (elementName == "line") { - v8event->line = readData.toInt(); - break; - } - - if (elementName == "totalTime") { - v8event->totalTime = readData.toDouble(); - cumulatedV8Time += v8event->totalTime; - break; - } - - if (elementName == "selfTime") { - v8event->selfTime = readData.toDouble(); - break; - } - } - - break; - } - case QXmlStreamReader::EndElement : { - if (elementName == "event") { - currentEvent = 0; - break; - } - if (elementName == "eventData") { - readingQmlEvents = false; - break; - } - if (elementName == "v8profile") { - readingV8Events = false; - } - } - default: break; - } - } - - file.close(); - - if (stream.hasError()) { - emit error(tr("Error while parsing %1").arg(filename)); - clear(); - return; - } - - stream.clear(); - - if (!validVersion) { - clear(); - emit countChanged(); - emit dataReady(); - emit error(tr("Invalid version of QML Trace file.")); - return; - } - - // backwards compatibility - if (d->m_v8MeasuredTime == 0) - d->m_v8MeasuredTime = cumulatedV8Time; - - // move the buffered data to the details cache - foreach (QmlEvent *desc, descriptionBuffer.values()) { - desc->eventHashStr = getHashStringForQmlEvent( - desc->location, desc->eventType);; - d->m_eventDescriptions[desc->eventHashStr] = desc; - } - - // sort startTimeSortedList - if (!startTimesAreSorted) { - qSort(d->m_startTimeSortedList.begin(), - d->m_startTimeSortedList.end(), compareStartTimes); - for (int i = 0; i< d->m_startTimeSortedList.length(); i++) { - QmlEventStartTime startTimeData = d->m_startTimeSortedList[i]; - d->m_endTimeSortedList[startTimeData.endTimeIndex].startTimeIndex = i; - } - qSort(d->m_endTimeSortedList.begin(), - d->m_endTimeSortedList.end(), compareStartIndexes); - } - - // find v8events' children and parents - foreach (int parentIndex, childrenIndexes.keys()) { - QStringList childrenStrings = - childrenIndexes.value(parentIndex).split(","); - QStringList childrenTimesStrings = - childrenTimes.value(parentIndex).split(", "); - QStringList parentTimesStrings = - parentTimes.value(parentIndex).split(", "); - for (int ndx = 0; ndx < childrenStrings.count(); ndx++) { - int childIndex = childrenStrings[ndx].toInt(); - if (v8eventBuffer.value(childIndex)) { - V8EventSub *newChild = new V8EventSub(v8eventBuffer[childIndex]); - V8EventSub *newParent = new V8EventSub(v8eventBuffer[parentIndex]); - if (childrenTimesStrings.count() > ndx) - newChild->totalTime = childrenTimesStrings[ndx].toDouble(); - if (parentTimesStrings.count() > ndx) - newParent->totalTime = parentTimesStrings[ndx].toDouble(); - v8eventBuffer[parentIndex]->childrenHash.insert( - newChild->reference->displayName, newChild); - v8eventBuffer[childIndex]->parentHash.insert( - newParent->reference->displayName, newParent); - } - } - } - // store v8 events - d->m_v8EventList = v8eventBuffer.values(); - - emit countChanged(); - - descriptionBuffer.clear(); - - emit postProcessing(); - d->collectV8Statistics(); - postProcess(); -} - -/////////////////////////////////////////////// -qint64 ProfileData::getStartTime(int index) const -{ - return d->m_startTimeSortedList[index].startTime; -} - -qint64 ProfileData::getEndTime(int index) const -{ - return d->m_startTimeSortedList[index].startTime + - d->m_startTimeSortedList[index].length; -} - -qint64 ProfileData::getDuration(int index) const -{ - return d->m_startTimeSortedList[index].length; -} - -int ProfileData::getType(int index) const -{ - return d->m_startTimeSortedList[index].description->eventType; -} - -int ProfileData::getNestingLevel(int index) const -{ - return d->m_startTimeSortedList[index].nestingLevel; -} - -int ProfileData::getNestingDepth(int index) const -{ - return d->m_startTimeSortedList[index].nestingDepth; -} - -QString ProfileData::getFilename(int index) const -{ - return d->m_startTimeSortedList[index].description->location.filename; -} - -int ProfileData::getLine(int index) const -{ - return d->m_startTimeSortedList[index].description->location.line; -} - -int ProfileData::getColumn(int index) const -{ - return d->m_startTimeSortedList[index].description->location.column; -} - -QString ProfileData::getDetails(int index) const -{ - // special: animations - if (d->m_startTimeSortedList[index].description->eventType == - QQmlProfilerService::Painting && - d->m_startTimeSortedList[index].animationCount >= 0) - return tr("%1 animations at %2 FPS").arg( - QString::number(d->m_startTimeSortedList[index].animationCount), - QString::number(d->m_startTimeSortedList[index].frameRate)); - return d->m_startTimeSortedList[index].description->details; -} - -int ProfileData::getEventId(int index) const { - return d->m_startTimeSortedList[index].description->eventId; -} - -int ProfileData::getFramerate(int index) const -{ - return d->m_startTimeSortedList[index].frameRate; -} - -int ProfileData::getAnimationCount(int index) const -{ - return d->m_startTimeSortedList[index].animationCount; -} - -int ProfileData::getMaximumAnimationCount() const -{ - return d->m_maximumAnimationCount; -} - -int ProfileData::getMinimumAnimationCount() const -{ - return d->m_minimumAnimationCount; -} - -int ProfileData::uniqueEventsOfType(int type) const -{ - if (!d->m_typeCounts.contains(type)) - return 0; - return d->m_typeCounts[type]->eventIds.count(); -} - -int ProfileData::maxNestingForType(int type) const -{ - if (!d->m_typeCounts.contains(type)) - return 0; - return d->m_typeCounts[type]->nestingCount; -} - -QString ProfileData::eventTextForType(int type, int index) const -{ - if (!d->m_typeCounts.contains(type)) - return QString(); - return d->m_eventDescriptions.values().at( - d->m_typeCounts[type]->eventIds[index])->details; -} - -QString ProfileData::eventDisplayNameForType(int type, int index) const -{ - if (!d->m_typeCounts.contains(type)) - return QString(); - return d->m_eventDescriptions.values().at( - d->m_typeCounts[type]->eventIds[index])->displayname; -} - -int ProfileData::eventIdForType(int type, int index) const -{ - if (!d->m_typeCounts.contains(type)) - return -1; - return d->m_typeCounts[type]->eventIds[index]; -} - -int ProfileData::eventPosInType(int index) const -{ - int eventType = d->m_startTimeSortedList[index].description->eventType; - return d->m_typeCounts[eventType]->eventIds.indexOf( - d->m_startTimeSortedList[index].description->eventId); -} diff --git a/tools/qmlprofiler/profiledata.h b/tools/qmlprofiler/profiledata.h deleted file mode 100644 index f5d726916c..0000000000 --- a/tools/qmlprofiler/profiledata.h +++ /dev/null @@ -1,247 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/ -** -** This file is part of the QtQml module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** GNU Lesser General Public License Usage -** This file may be used under the terms of the GNU Lesser General Public -** License version 2.1 as published by the Free Software Foundation and -** appearing in the file LICENSE.LGPL included in the packaging of this -** file. Please review the following information to ensure the GNU Lesser -** General Public License version 2.1 requirements will be met: -** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Nokia gives you certain additional -** rights. These rights are described in the Nokia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU General -** Public License version 3.0 as published by the Free Software Foundation -** and appearing in the file LICENSE.GPL included in the packaging of this -** file. Please review the following information to ensure the GNU General -** Public License version 3.0 requirements will be met: -** http://www.gnu.org/copyleft/gpl.html. -** -** Other Usage -** Alternatively, this file may be used in accordance with the terms and -** conditions contained in a signed written agreement between you and Nokia. -** -** -** -** -** -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef PROFILEDATA_H -#define PROFILEDATA_H - -#include - -#include -#include - -struct QmlEvent; -struct V8Event; - -typedef QHash QmlEventHash; -typedef QList QmlEvents; -typedef QList V8Events; - -struct EventLocation -{ - EventLocation() : line(-1),column(-1) {} - EventLocation(const QString &file, int lineNumber, int columnNumber) - : filename(file), line(lineNumber), column(columnNumber) {} - QString filename; - int line; - int column; -}; - -struct QmlEventSub { - QmlEventSub(QmlEvent *from) - : reference(from), duration(0), calls(0), inLoopPath(false) - {} - QmlEventSub(QmlEventSub *from) - : reference(from->reference), duration(from->duration), - calls(from->calls), inLoopPath(from->inLoopPath) - {} - QmlEvent *reference; - qint64 duration; - qint64 calls; - bool inLoopPath; -}; - -struct QmlEvent -{ - QmlEvent(); - ~QmlEvent(); - - QString displayname; - QString eventHashStr; - QString details; - EventLocation location; - QQmlProfilerService::RangeType eventType; - QHash parentHash; - QHash childrenHash; - qint64 duration; - qint64 calls; - qint64 minTime; - qint64 maxTime; - double timePerCall; - double percentOfTime; - qint64 medianTime; - int eventId; - bool isBindingLoop; - - QmlEvent &operator=(const QmlEvent &ref); -}; - -struct V8EventSub { - V8EventSub(V8Event *from) - : reference(from), totalTime(0) - {} - V8EventSub(V8EventSub *from) - : reference(from->reference), totalTime(from->totalTime) - {} - - V8Event *reference; - qint64 totalTime; -}; - -struct V8Event -{ - V8Event(); - ~V8Event(); - - QString displayName; - QString filename; - QString functionName; - int line; - double totalTime; // given in milliseconds - double totalPercent; - double selfTime; - double selfPercent; - QHash parentHash; - QHash childrenHash; - int eventId; - - V8Event &operator=(const V8Event &ref); -}; - -class ProfileData : public QObject -{ - Q_OBJECT - -public: - explicit ProfileData(QObject *parent = 0); - ~ProfileData(); - - QmlEvents getQmlEvents() const; - QmlEvent *qmlEvent(int eventId) const; - const V8Events& getV8Events() const; - V8Event *v8Event(int eventId) const; - - int findFirstIndex(qint64 startTime) const; - int findFirstIndexNoParents(qint64 startTime) const; - int findLastIndex(qint64 endTime) const; - Q_INVOKABLE qint64 firstTimeMark() const; - Q_INVOKABLE qint64 lastTimeMark() const; - - Q_INVOKABLE int count() const; - - // data access - Q_INVOKABLE qint64 getStartTime(int index) const; - Q_INVOKABLE qint64 getEndTime(int index) const; - Q_INVOKABLE qint64 getDuration(int index) const; - Q_INVOKABLE int getType(int index) const; - Q_INVOKABLE int getNestingLevel(int index) const; - Q_INVOKABLE int getNestingDepth(int index) const; - Q_INVOKABLE QString getFilename(int index) const; - Q_INVOKABLE int getLine(int index) const; - Q_INVOKABLE int getColumn(int index) const; - Q_INVOKABLE QString getDetails(int index) const; - Q_INVOKABLE int getEventId(int index) const; - Q_INVOKABLE int getFramerate(int index) const; - Q_INVOKABLE int getAnimationCount(int index) const; - Q_INVOKABLE int getMaximumAnimationCount() const; - Q_INVOKABLE int getMinimumAnimationCount() const; - - // per-type data - Q_INVOKABLE int uniqueEventsOfType(int type) const; - Q_INVOKABLE int maxNestingForType(int type) const; - Q_INVOKABLE QString eventTextForType(int type, int index) const; - Q_INVOKABLE QString eventDisplayNameForType(int type, int index) const; - Q_INVOKABLE int eventIdForType(int type, int index) const; - Q_INVOKABLE int eventPosInType(int index) const; - - Q_INVOKABLE qint64 traceStartTime() const; - Q_INVOKABLE qint64 traceEndTime() const; - Q_INVOKABLE qint64 traceDuration() const; - Q_INVOKABLE qint64 qmlMeasuredTime() const; - Q_INVOKABLE qint64 v8MeasuredTime() const; - - void showErrorDialog(const QString &st ) const; - void compileStatistics(qint64 startTime, qint64 endTime); - -signals: - void dataReady(); - void countChanged(); - void error(const QString &error); - void dataClear(); - void processingData(); - void postProcessing(); - - void requestDetailsForLocation(int eventType, const EventLocation &location); - void detailsChanged(int eventId, const QString &newString); - void reloadDetailLabels(); - void reloadDocumentsForDetails(); - -public slots: - void clear(); - void addQmlEvent(QQmlProfilerService::RangeType type, - qint64 startTime, qint64 length, - const QStringList &data, - const EventLocation &location); - void complete(); - - void addV8Event(int depth,const QString &function,const QString &filename, - int lineNumber, double totalTime, double selfTime); - void addFrameEvent(qint64 time, int framerate, int animationcount); - bool save(const QString &filename); - void load(const QString &filename); - void setFilename(const QString &filename); - void load(); - - void setTraceEndTime( qint64 time ); - void setTraceStartTime( qint64 time ); - - void rewriteDetailsString(QQmlProfilerService::RangeType eventType, - const EventLocation &location, - const QString &newString); - void finishedRewritingDetails(); - -private: - void postProcess(); - void sortEndTimes(); - void findAnimationLimits(); - void sortStartTimes(); - void computeLevels(); - void computeNestingLevels(); - void computeNestingDepth(); - void prepareForDisplay(); - void linkEndsToStarts(); - void reloadDetails(); - void findBindingLoops(qint64 startTime, qint64 endTime); - -private: - class ProfileDataPrivate *d; -}; - -#endif // PROFILEDATA_H diff --git a/tools/qmlprofiler/qmlprofiler.pro b/tools/qmlprofiler/qmlprofiler.pro index db7e357b70..c35c487a1d 100644 --- a/tools/qmlprofiler/qmlprofiler.pro +++ b/tools/qmlprofiler/qmlprofiler.pro @@ -14,14 +14,15 @@ CONFIG += console declarative_debug SOURCES += main.cpp \ qmlprofilerapplication.cpp \ commandlistener.cpp \ - profileclient.cpp \ - profiledata.cpp \ - qqmldebugclient.cpp + qqmldebugclient.cpp \ + qmlprofilerdata.cpp \ + qmlprofilerclient.cpp HEADERS += \ qmlprofilerapplication.h \ commandlistener.h \ constants.h \ - profileclient.h \ - profiledata.h \ + qmlprofilerdata.h \ + qmlprofilerclient.h \ + qmlprofilereventlocation.h \ qqmldebugclient.h diff --git a/tools/qmlprofiler/qmlprofilerapplication.cpp b/tools/qmlprofiler/qmlprofilerapplication.cpp index 0b10ec0b15..05518dd167 100644 --- a/tools/qmlprofiler/qmlprofilerapplication.cpp +++ b/tools/qmlprofiler/qmlprofilerapplication.cpp @@ -104,20 +104,20 @@ QmlProfilerApplication::QmlProfilerApplication(int &argc, char **argv) : connect(&m_qmlProfilerClient, SIGNAL(enabledChanged()), this, SLOT(traceClientEnabled())); connect(&m_qmlProfilerClient, SIGNAL(recordingChanged(bool)), this, SLOT(recordingChanged())); - connect(&m_qmlProfilerClient, SIGNAL(range(QQmlProfilerService::RangeType,qint64,qint64,QStringList,EventLocation)), - &m_profileData, SLOT(addQmlEvent(QQmlProfilerService::RangeType,qint64,qint64,QStringList,EventLocation))); - connect(&m_qmlProfilerClient, SIGNAL(traceFinished(qint64)), &m_profileData, SLOT(setTraceEndTime(qint64))); - connect(&m_qmlProfilerClient, SIGNAL(traceStarted(qint64)), &m_profileData, SLOT(setTraceStartTime(qint64))); - connect(&m_qmlProfilerClient, SIGNAL(frame(qint64,int,int)), &m_profileData, SLOT(addFrameEvent(qint64,int,int))); + connect(&m_qmlProfilerClient, SIGNAL(range(QQmlProfilerService::RangeType,qint64,qint64,QStringList,QmlEventLocation)), + &m_profilerData, SLOT(addQmlEvent(QQmlProfilerService::RangeType,qint64,qint64,QStringList,QmlEventLocation))); + connect(&m_qmlProfilerClient, SIGNAL(traceFinished(qint64)), &m_profilerData, SLOT(setTraceEndTime(qint64))); + connect(&m_qmlProfilerClient, SIGNAL(traceStarted(qint64)), &m_profilerData, SLOT(setTraceStartTime(qint64))); + connect(&m_qmlProfilerClient, SIGNAL(frame(qint64,int,int)), &m_profilerData, SLOT(addFrameEvent(qint64,int,int))); connect(&m_qmlProfilerClient, SIGNAL(complete()), this, SLOT(qmlComplete())); connect(&m_v8profilerClient, SIGNAL(enabledChanged()), this, SLOT(profilerClientEnabled())); connect(&m_v8profilerClient, SIGNAL(range(int,QString,QString,int,double,double)), - &m_profileData, SLOT(addV8Event(int,QString,QString,int,double,double))); + &m_profilerData, SLOT(addV8Event(int,QString,QString,int,double,double))); connect(&m_v8profilerClient, SIGNAL(complete()), this, SLOT(v8Complete())); - connect(&m_profileData, SIGNAL(error(QString)), this, SLOT(logError(QString))); - connect(&m_profileData, SIGNAL(dataReady()), this, SLOT(traceFinished())); + connect(&m_profilerData, SIGNAL(error(QString)), this, SLOT(logError(QString))); + connect(&m_profilerData, SIGNAL(dataReady()), this, SLOT(traceFinished())); } @@ -375,7 +375,7 @@ void QmlProfilerApplication::traceFinished() { const QString fileName = traceFileName(); - if (m_profileData.save(fileName)) + if (m_profilerData.save(fileName)) print(QString("Saving trace to %1.").arg(fileName)); if (m_quitAfterSave) @@ -416,7 +416,7 @@ void QmlProfilerApplication::qmlComplete() m_qmlDataReady = true; if (m_v8profilerClient.state() != QQmlDebugClient::Enabled || m_v8DataReady) { - m_profileData.complete(); + m_profilerData.complete(); // once complete is sent, reset the flag m_qmlDataReady = false; } @@ -427,7 +427,7 @@ void QmlProfilerApplication::v8Complete() m_v8DataReady = true; if (m_qmlProfilerClient.state() != QQmlDebugClient::Enabled || m_qmlDataReady) { - m_profileData.complete(); + m_profilerData.complete(); // once complete is sent, reset the flag m_v8DataReady = false; } diff --git a/tools/qmlprofiler/qmlprofilerapplication.h b/tools/qmlprofiler/qmlprofilerapplication.h index 3937db7e5f..c7148880ad 100644 --- a/tools/qmlprofiler/qmlprofilerapplication.h +++ b/tools/qmlprofiler/qmlprofilerapplication.h @@ -46,7 +46,8 @@ #include #include -#include "profileclient.h" +#include "qmlprofilerclient.h" +#include "qmlprofilerdata.h" class QmlProfilerApplication : public QCoreApplication { @@ -104,9 +105,9 @@ private: bool m_quitAfterSave; QQmlDebugConnection m_connection; - QmlProfileClient m_qmlProfilerClient; - V8ProfileClient m_v8profilerClient; - ProfileData m_profileData; + QmlProfilerClient m_qmlProfilerClient; + V8ProfilerClient m_v8profilerClient; + QmlProfilerData m_profilerData; QTimer m_connectTimer; uint m_connectionAttempts; diff --git a/tools/qmlprofiler/qmlprofilerclient.cpp b/tools/qmlprofiler/qmlprofilerclient.cpp new file mode 100644 index 0000000000..97ed90e890 --- /dev/null +++ b/tools/qmlprofiler/qmlprofilerclient.cpp @@ -0,0 +1,300 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qmlprofilerclient.h" + +#include +#include + +ProfilerClient::ProfilerClient(const QString &clientName, + QQmlDebugConnection *client) + : QQmlDebugClient(clientName, client), + m_recording(false), + m_enabled(false) +{ +} + +ProfilerClient::~ProfilerClient() +{ + //Disable profiling if started by client + //Profiling data will be lost!! + if (isRecording()) + setRecording(false); +} + +void ProfilerClient::clearData() +{ + emit cleared(); +} + +bool ProfilerClient::isEnabled() const +{ + return m_enabled; +} + +void ProfilerClient::sendRecordingStatus() +{ +} + +bool ProfilerClient::isRecording() const +{ + return m_recording; +} + +void ProfilerClient::setRecording(bool v) +{ + if (v == m_recording) + return; + + m_recording = v; + + if (state() == Enabled) { + sendRecordingStatus(); + } + + emit recordingChanged(v); +} + +void ProfilerClient::stateChanged(State status) +{ + if ((m_enabled && status != Enabled) || + (!m_enabled && status == Enabled)) + emit enabledChanged(); + + m_enabled = status == Enabled; + +} + +class QmlProfilerClientPrivate +{ +public: + QmlProfilerClientPrivate() + : inProgressRanges(0) + , maximumTime(0) + { + ::memset(rangeCount, 0, + QQmlProfilerService::MaximumRangeType * sizeof(int)); + } + + qint64 inProgressRanges; + QStack rangeStartTimes[QQmlProfilerService::MaximumRangeType]; + QStack rangeDatas[QQmlProfilerService::MaximumRangeType]; + QStack rangeLocations[QQmlProfilerService::MaximumRangeType]; + int rangeCount[QQmlProfilerService::MaximumRangeType]; + qint64 maximumTime; +}; + +QmlProfilerClient::QmlProfilerClient( + QQmlDebugConnection *client) + : ProfilerClient(QStringLiteral("CanvasFrameRate"), client), + d(new QmlProfilerClientPrivate) +{ +} + +QmlProfilerClient::~QmlProfilerClient() +{ + delete d; +} + +void QmlProfilerClient::clearData() +{ + ::memset(d->rangeCount, 0, + QQmlProfilerService::MaximumRangeType * sizeof(int)); + ProfilerClient::clearData(); +} + +void QmlProfilerClient::sendRecordingStatus() +{ + QByteArray ba; + QDataStream stream(&ba, QIODevice::WriteOnly); + stream << isRecording(); + sendMessage(ba); +} + +void QmlProfilerClient::messageReceived(const QByteArray &data) +{ + QByteArray rwData = data; + QDataStream stream(&rwData, QIODevice::ReadOnly); + + qint64 time; + int messageType; + + stream >> time >> messageType; + + if (messageType >= QQmlProfilerService::MaximumMessage) + return; + + if (messageType == QQmlProfilerService::Event) { + int event; + stream >> event; + + if (event == QQmlProfilerService::EndTrace) { + emit this->traceFinished(time); + d->maximumTime = time; + d->maximumTime = qMax(time, d->maximumTime); + } else if (event == QQmlProfilerService::AnimationFrame) { + int frameRate, animationCount; + stream >> frameRate >> animationCount; + emit this->frame(time, frameRate, animationCount); + d->maximumTime = qMax(time, d->maximumTime); + } else if (event == QQmlProfilerService::StartTrace) { + emit this->traceStarted(time); + d->maximumTime = time; + } else if (event < QQmlProfilerService::MaximumEventType) { + d->maximumTime = qMax(time, d->maximumTime); + } + } else if (messageType == QQmlProfilerService::Complete) { + emit complete(); + + } else { + int range; + stream >> range; + + if (range >= QQmlProfilerService::MaximumRangeType) + return; + + if (messageType == QQmlProfilerService::RangeStart) { + d->rangeStartTimes[range].push(time); + d->inProgressRanges |= (static_cast(1) << range); + ++d->rangeCount[range]; + } else if (messageType == QQmlProfilerService::RangeData) { + QString data; + stream >> data; + + int count = d->rangeCount[range]; + if (count > 0) { + while (d->rangeDatas[range].count() < count) + d->rangeDatas[range].push(QStringList()); + d->rangeDatas[range][count-1] << data; + } + + } else if (messageType == QQmlProfilerService::RangeLocation) { + QString fileName; + int line; + int column = -1; + stream >> fileName >> line; + + if (!stream.atEnd()) + stream >> column; + + if (d->rangeCount[range] > 0) { + d->rangeLocations[range].push(QmlEventLocation(fileName, line, + column)); + } + } else { + if (d->rangeCount[range] > 0) { + --d->rangeCount[range]; + if (d->inProgressRanges & (static_cast(1) << range)) + d->inProgressRanges &= ~(static_cast(1) << range); + + d->maximumTime = qMax(time, d->maximumTime); + QStringList data = d->rangeDatas[range].count() ? + d->rangeDatas[range].pop() : QStringList(); + QmlEventLocation location = d->rangeLocations[range].count() ? + d->rangeLocations[range].pop() : QmlEventLocation(); + + qint64 startTime = d->rangeStartTimes[range].pop(); + emit this->range((QQmlProfilerService::RangeType)range, + startTime, time - startTime, data, location); + if (d->rangeCount[range] == 0) { + int count = d->rangeDatas[range].count() + + d->rangeStartTimes[range].count() + + d->rangeLocations[range].count(); + if (count != 0) + qWarning() << "incorrectly nested data"; + } + } + } + } +} + +V8ProfilerClient::V8ProfilerClient(QQmlDebugConnection *client) + : ProfilerClient(QStringLiteral("V8Profiler"), client) +{ +} + +V8ProfilerClient::~V8ProfilerClient() +{ +} + +void V8ProfilerClient::sendRecordingStatus() +{ + QByteArray ba; + QDataStream stream(&ba, QIODevice::WriteOnly); + QByteArray cmd("V8PROFILER"); + QByteArray option(""); + QByteArray title(""); + + if (m_recording) { + option = "start"; + } else { + option = "stop"; + } + stream << cmd << option << title; + sendMessage(ba); +} + +void V8ProfilerClient::messageReceived(const QByteArray &data) +{ + QByteArray rwData = data; + QDataStream stream(&rwData, QIODevice::ReadOnly); + + int messageType; + + stream >> messageType; + + if (messageType == V8Complete) { + emit complete(); + } else if (messageType == V8Entry) { + QString filename; + QString function; + int lineNumber; + double totalTime; + double selfTime; + int depth; + + stream >> filename >> function >> lineNumber >> totalTime >> + selfTime >> depth; + emit this->range(depth, function, filename, lineNumber, totalTime, + selfTime); + } +} + diff --git a/tools/qmlprofiler/qmlprofilerclient.h b/tools/qmlprofiler/qmlprofilerclient.h new file mode 100644 index 0000000000..2a8629d1fb --- /dev/null +++ b/tools/qmlprofiler/qmlprofilerclient.h @@ -0,0 +1,138 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMLPROFILERCLIENT_H +#define QMLPROFILERCLIENT_H + +#include "qqmldebugclient.h" +#include +#include "qmlprofilereventlocation.h" + +class ProfilerClientPrivate; +class ProfilerClient : public QQmlDebugClient +{ + Q_OBJECT + + Q_PROPERTY(bool enabled READ isEnabled NOTIFY enabledChanged) + Q_PROPERTY(bool recording READ isRecording WRITE setRecording + NOTIFY recordingChanged) + +public: + ProfilerClient(const QString &clientName, + QQmlDebugConnection *client); + ~ProfilerClient(); + + bool isEnabled() const; + bool isRecording() const; + +public slots: + void setRecording(bool); + virtual void clearData(); + virtual void sendRecordingStatus(); + +signals: + void complete(); + void recordingChanged(bool arg); + void enabledChanged(); + void cleared(); + +protected: + virtual void stateChanged(State); + +protected: + bool m_recording; + bool m_enabled; +}; + +class QmlProfilerClient : public ProfilerClient +{ + Q_OBJECT + +public: + QmlProfilerClient(QQmlDebugConnection *client); + ~QmlProfilerClient(); + +public slots: + void clearData(); + void sendRecordingStatus(); + +signals: + void traceFinished( qint64 time ); + void traceStarted( qint64 time ); + void range(QQmlProfilerService::RangeType type, qint64 startTime, + qint64 length, const QStringList &data, + const QmlEventLocation &location); + void frame(qint64 time, int frameRate, int animationCount); + +protected: + virtual void messageReceived(const QByteArray &); + +private: + class QmlProfilerClientPrivate *d; +}; + +class V8ProfilerClient : public ProfilerClient +{ + Q_OBJECT + +public: + enum Message { + V8Entry, + V8Complete, + + V8MaximumMessage + }; + + V8ProfilerClient(QQmlDebugConnection *client); + ~V8ProfilerClient(); + +public slots: + void sendRecordingStatus(); + +signals: + void range(int depth, const QString &function, const QString &filename, + int lineNumber, double totalTime, double selfTime); + +protected: + virtual void messageReceived(const QByteArray &); +}; + +#endif // QMLPROFILERCLIENT_H diff --git a/tools/qmlprofiler/qmlprofilerdata.cpp b/tools/qmlprofiler/qmlprofilerdata.cpp new file mode 100644 index 0000000000..38e64ff46e --- /dev/null +++ b/tools/qmlprofiler/qmlprofilerdata.cpp @@ -0,0 +1,602 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qmlprofilerdata.h" + +#include +#include +#include +#include +#include + +namespace Constants { + const char TYPE_PAINTING_STR[] = "Painting"; + const char TYPE_COMPILING_STR[] = "Compiling"; + const char TYPE_CREATING_STR[] = "Creating"; + const char TYPE_BINDING_STR[] = "Binding"; + const char TYPE_HANDLINGSIGNAL_STR[] = "HandlingSignal"; + const char PROFILER_FILE_VERSION[] = "1.02"; +} + +struct QmlRangeEventData { + QmlRangeEventData() {} // never called + QmlRangeEventData(const QString &_displayName, + const QString &_eventHashStr, + const QmlEventLocation &_location, + const QString &_details, + const QQmlProfilerService::RangeType &_eventType) + : displayName(_displayName),eventHashStr(_eventHashStr),location(_location), + details(_details),eventType(_eventType) {} + QString displayName; + QString eventHashStr; + QmlEventLocation location; + QString details; + QQmlProfilerService::RangeType eventType; +}; + +struct QmlRangeEventStartInstance { + QmlRangeEventStartInstance() {} // never called + QmlRangeEventStartInstance(qint64 _startTime, qint64 _duration, int _frameRate, + int _animationCount, QmlRangeEventData *_data) + : startTime(_startTime), duration(_duration), frameRate(_frameRate), + animationCount(_animationCount), data(_data) + { } + qint64 startTime; + qint64 duration; + int frameRate; + int animationCount; + QmlRangeEventData *data; +}; + +QT_BEGIN_NAMESPACE +Q_DECLARE_TYPEINFO(QmlRangeEventData, Q_MOVABLE_TYPE); +Q_DECLARE_TYPEINFO(QmlRangeEventStartInstance, Q_MOVABLE_TYPE); +QT_END_NAMESPACE + +struct QV8EventInfo { + QString displayName; + QString eventHashStr; + QString functionName; + QString fileName; + int line; + qint64 totalTime; + qint64 selfTime; + + QHash v8children; +}; + +///////////////////////////////////////////////////////////////// +class QmlProfilerDataPrivate +{ +public: + QmlProfilerDataPrivate(QmlProfilerData *qq){ Q_UNUSED(qq); } + + // data storage + QHash eventDescriptions; + QVector startInstanceList; + QHash v8EventHash; + + qint64 traceStartTime; + qint64 traceEndTime; + + // internal state while collecting events + QmlRangeEventStartInstance *lastFrameEvent; + qint64 qmlMeasuredTime; + qint64 v8MeasuredTime; + QHash v8parents; + void clearV8RootEvent(); + QV8EventInfo v8RootEvent; + + QmlProfilerData::State state; +}; + +///////////////////////////////////////////////////////////////// +QmlProfilerData::QmlProfilerData(QObject *parent) : + QObject(parent),d(new QmlProfilerDataPrivate(this)) +{ + d->state = Empty; + clear(); +} + +QmlProfilerData::~QmlProfilerData() +{ + clear(); + delete d; +} + +void QmlProfilerData::clear() +{ + qDeleteAll(d->eventDescriptions.values()); + d->eventDescriptions.clear(); + d->startInstanceList.clear(); + + qDeleteAll(d->v8EventHash.values()); + d->v8EventHash.clear(); + d->v8parents.clear(); + d->clearV8RootEvent(); + d->v8MeasuredTime = 0; + + d->traceEndTime = 0; + d->traceStartTime = -1; + d->qmlMeasuredTime = 0; + + d->lastFrameEvent = 0; + + setState(Empty); +} + +QString QmlProfilerData::getHashStringForQmlEvent(const QmlEventLocation &location, int eventType) +{ + return QString(QStringLiteral("%1:%2:%3:%4")).arg( + location.filename, + QString::number(location.line), + QString::number(location.column), + QString::number(eventType)); +} + +QString QmlProfilerData::getHashStringForV8Event(const QString &displayName, const QString &function) +{ + return QString(QStringLiteral("%1:%2")).arg(displayName, function); +} + +QString QmlProfilerData::qmlRangeTypeAsString(QQmlProfilerService::RangeType typeEnum) +{ + switch (typeEnum) { + case QQmlProfilerService::Painting: + return QLatin1String(Constants::TYPE_PAINTING_STR); + break; + case QQmlProfilerService::Compiling: + return QLatin1String(Constants::TYPE_COMPILING_STR); + break; + case QQmlProfilerService::Creating: + return QLatin1String(Constants::TYPE_CREATING_STR); + break; + case QQmlProfilerService::Binding: + return QLatin1String(Constants::TYPE_BINDING_STR); + break; + case QQmlProfilerService::HandlingSignal: + return QLatin1String(Constants::TYPE_HANDLINGSIGNAL_STR); + break; + default: + return QString::number((int)typeEnum); + } +} + +void QmlProfilerData::setTraceStartTime(qint64 time) +{ + d->traceStartTime = time; +} + +void QmlProfilerData::setTraceEndTime(qint64 time) +{ + d->traceEndTime = time; +} + +qint64 QmlProfilerData::traceStartTime() const +{ + return d->traceStartTime; +} + +qint64 QmlProfilerData::traceEndTime() const +{ + return d->traceEndTime; +} + +void QmlProfilerData::addQmlEvent(QQmlProfilerService::RangeType type, + qint64 startTime, + qint64 duration, + const QStringList &data, + const QmlEventLocation &location) +{ + setState(AcquiringData); + + QString details; + // generate details string + if (data.isEmpty()) + details = tr("Source code not available"); + else { + details = data.join(QStringLiteral(" ")).replace( + QLatin1Char('\n'),QStringLiteral(" ")).simplified(); + QRegExp rewrite(QStringLiteral("\\(function \\$(\\w+)\\(\\) \\{ (return |)(.+) \\}\\)")); + bool match = rewrite.exactMatch(details); + if (match) { + details = rewrite.cap(1) +QLatin1String(": ") + rewrite.cap(3); + } + if (details.startsWith(QLatin1String("file://"))) + details = details.mid(details.lastIndexOf(QLatin1Char('/')) + 1); + } + + QmlEventLocation eventLocation = location; + QString displayName, eventHashStr; + // generate hash + if (eventLocation.filename.isEmpty()) { + displayName = tr(""); + eventHashStr = getHashStringForQmlEvent(eventLocation, type); + } else { + const QString filePath = QUrl(eventLocation.filename).path(); + displayName = filePath.mid( + filePath.lastIndexOf(QLatin1Char('/')) + 1) + + QLatin1Char(':') + QString::number(eventLocation.line); + eventHashStr = getHashStringForQmlEvent(eventLocation, type); + } + + QmlRangeEventData *newEvent; + if (d->eventDescriptions.contains(eventHashStr)) { + newEvent = d->eventDescriptions[eventHashStr]; + } else { + newEvent = new QmlRangeEventData(displayName, eventHashStr, location, details, type); + d->eventDescriptions.insert(eventHashStr, newEvent); + } + + QmlRangeEventStartInstance rangeEventStartInstance(startTime, duration, 1e9/duration, -1, newEvent); + + d->startInstanceList.append(rangeEventStartInstance); +} + +void QmlProfilerData::addFrameEvent(qint64 time, int framerate, int animationcount) +{ + setState(AcquiringData); + + QString details = tr("Animation Timer Update"); + QString displayName = tr(""); + QString eventHashStr = displayName; + + QmlRangeEventData *newEvent; + if (d->eventDescriptions.contains(eventHashStr)) { + newEvent = d->eventDescriptions[eventHashStr]; + } else { + newEvent = new QmlRangeEventData(displayName, eventHashStr, QmlEventLocation(), details, QQmlProfilerService::Painting); + d->eventDescriptions.insert(eventHashStr, newEvent); + } + + qint64 duration = 1e9/framerate; + // avoid overlap + if (d->lastFrameEvent && + d->lastFrameEvent->startTime + d->lastFrameEvent->duration >= time) { + d->lastFrameEvent->duration = time - 1 - d->lastFrameEvent->startTime; + } + + QmlRangeEventStartInstance rangeEventStartInstance(time, duration, framerate, animationcount, newEvent); + + d->startInstanceList.append(rangeEventStartInstance); + + d->lastFrameEvent = &d->startInstanceList.last(); +} + +QString QmlProfilerData::rootEventName() +{ + return tr(""); +} + +QString QmlProfilerData::rootEventDescription() +{ + return tr("Main Program"); +} + +void QmlProfilerDataPrivate::clearV8RootEvent() +{ + v8RootEvent.displayName = QmlProfilerData::rootEventName(); + v8RootEvent.eventHashStr = QmlProfilerData::rootEventName(); + v8RootEvent.functionName = QmlProfilerData::rootEventDescription(); + v8RootEvent.line = -1; + v8RootEvent.totalTime = 0; + v8RootEvent.selfTime = 0; + v8RootEvent.v8children.clear(); +} + +void QmlProfilerData::addV8Event(int depth, const QString &function, const QString &filename, + int lineNumber, double totalTime, double selfTime) +{ + QString displayName = filename.mid(filename.lastIndexOf(QLatin1Char('/')) + 1) + + QLatin1Char(':') + QString::number(lineNumber); + QString hashStr = getHashStringForV8Event(displayName, function); + + setState(AcquiringData); + + // time is given in milliseconds, but internally we store it in microseconds + totalTime *= 1e6; + selfTime *= 1e6; + + // accumulate information + QV8EventInfo *eventData = d->v8EventHash[hashStr]; + if (!eventData) { + eventData = new QV8EventInfo; + eventData->displayName = displayName; + eventData->eventHashStr = hashStr; + eventData->fileName = filename; + eventData->functionName = function; + eventData->line = lineNumber; + eventData->totalTime = totalTime; + eventData->selfTime = selfTime; + d->v8EventHash[hashStr] = eventData; + } else { + eventData->totalTime += totalTime; + eventData->selfTime += selfTime; + } + d->v8parents[depth] = eventData; + + QV8EventInfo *parentEvent = 0; + if (depth == 0) { + parentEvent = &d->v8RootEvent; + d->v8MeasuredTime += totalTime; + } + if (depth > 0 && d->v8parents.contains(depth-1)) { + parentEvent = d->v8parents.value(depth-1); + } + + if (parentEvent != 0) { + if (!parentEvent->v8children.contains(eventData->eventHashStr)) { + parentEvent->v8children[eventData->eventHashStr] = totalTime; + } else { + parentEvent->v8children[eventData->eventHashStr] += totalTime; + } + } +} + +void QmlProfilerData::computeQmlTime() +{ + // compute levels + QHash endtimesPerLevel; + int minimumLevel = 1; + int level = minimumLevel; + + for (int i = 0; i < d->startInstanceList.count(); i++) { + qint64 st = d->startInstanceList[i].startTime; + int type = d->startInstanceList[i].data->eventType; + + if (type == QQmlProfilerService::Painting) { + continue; + } + + // general level + if (endtimesPerLevel[level] > st) { + level++; + } else { + while (level > minimumLevel && endtimesPerLevel[level-1] <= st) + level--; + } + endtimesPerLevel[level] = st + d->startInstanceList[i].duration; + + if (level == minimumLevel) { + d->qmlMeasuredTime += d->startInstanceList[i].duration; + } + } +} + +bool compareStartTimes(const QmlRangeEventStartInstance &t1, const QmlRangeEventStartInstance &t2) +{ + return t1.startTime < t2.startTime; +} + +void QmlProfilerData::sortStartTimes() +{ + if (d->startInstanceList.count() < 2) + return; + + // assuming startTimes is partially sorted + // identify blocks of events and sort them with quicksort + QVector::iterator itFrom = d->startInstanceList.end() - 2; + QVector::iterator itTo = d->startInstanceList.end() - 1; + + while (itFrom != d->startInstanceList.begin() && itTo != d->startInstanceList.begin()) { + // find block to sort + while ( itFrom != d->startInstanceList.begin() + && itTo->startTime > itFrom->startTime ) { + itTo--; + itFrom = itTo - 1; + } + + // if we're at the end of the list + if (itFrom == d->startInstanceList.begin()) + break; + + // find block length + while ( itFrom != d->startInstanceList.begin() + && itTo->startTime <= itFrom->startTime ) + itFrom--; + + if (itTo->startTime <= itFrom->startTime) + qSort(itFrom, itTo + 1, compareStartTimes); + else + qSort(itFrom + 1, itTo + 1, compareStartTimes); + + // move to next block + itTo = itFrom; + itFrom = itTo - 1; + } +} + +void QmlProfilerData::complete() +{ + setState(ProcessingData); + sortStartTimes(); + computeQmlTime(); + setState(Done); + emit dataReady(); +} + +bool QmlProfilerData::isEmpty() const +{ + return d->startInstanceList.isEmpty() && d->v8EventHash.isEmpty(); +} + +bool QmlProfilerData::save(const QString &filename) +{ + if (isEmpty()) { + emit error(tr("No data to save")); + return false; + } + + QFile file(filename); + if (!file.open(QIODevice::WriteOnly)) { + emit error(tr("Could not open %1 for writing").arg(filename)); + return false; + } + + QXmlStreamWriter stream(&file); + stream.setAutoFormatting(true); + stream.writeStartDocument(); + + stream.writeStartElement(QStringLiteral("trace")); + stream.writeAttribute(QStringLiteral("version"), QLatin1String(Constants::PROFILER_FILE_VERSION)); + + stream.writeAttribute(QStringLiteral("traceStart"), QString::number(traceStartTime())); + stream.writeAttribute(QStringLiteral("traceEnd"), QString::number(traceEndTime())); + + stream.writeStartElement(QStringLiteral("eventData")); + stream.writeAttribute(QStringLiteral("totalTime"), QString::number(d->qmlMeasuredTime)); + + foreach (const QmlRangeEventData *eventData, d->eventDescriptions.values()) { + stream.writeStartElement(QStringLiteral("event")); + stream.writeAttribute(QStringLiteral("index"), QString::number(d->eventDescriptions.keys().indexOf(eventData->eventHashStr))); + stream.writeTextElement(QStringLiteral("displayname"), eventData->displayName); + stream.writeTextElement(QStringLiteral("type"), qmlRangeTypeAsString(eventData->eventType)); + if (!eventData->location.filename.isEmpty()) { + stream.writeTextElement(QStringLiteral("filename"), eventData->location.filename); + stream.writeTextElement(QStringLiteral("line"), QString::number(eventData->location.line)); + stream.writeTextElement(QStringLiteral("column"), QString::number(eventData->location.column)); + } + stream.writeTextElement(QStringLiteral("details"), eventData->details); + stream.writeEndElement(); + } + stream.writeEndElement(); // eventData + + stream.writeStartElement(QStringLiteral("profilerDataModel")); + foreach (const QmlRangeEventStartInstance &rangedEvent, d->startInstanceList) { + stream.writeStartElement(QStringLiteral("range")); + stream.writeAttribute(QStringLiteral("startTime"), QString::number(rangedEvent.startTime)); + stream.writeAttribute(QStringLiteral("duration"), QString::number(rangedEvent.duration)); + stream.writeAttribute(QStringLiteral("eventIndex"), QString::number(d->eventDescriptions.keys().indexOf(rangedEvent.data->eventHashStr))); + if (rangedEvent.data->eventType == QQmlProfilerService::Painting && rangedEvent.animationCount >= 0) { + // animation frame + stream.writeAttribute(QStringLiteral("framerate"), QString::number(rangedEvent.frameRate)); + stream.writeAttribute(QStringLiteral("animationcount"), QString::number(rangedEvent.animationCount)); + } + stream.writeEndElement(); + } + stream.writeEndElement(); // profilerDataModel + + stream.writeStartElement(QStringLiteral("v8profile")); // v8 profiler output + stream.writeAttribute(QStringLiteral("totalTime"), QString::number(d->v8MeasuredTime)); + foreach (QV8EventInfo *v8event, d->v8EventHash.values()) { + stream.writeStartElement(QStringLiteral("event")); + stream.writeAttribute(QStringLiteral("index"), QString::number(d->v8EventHash.keys().indexOf(v8event->eventHashStr))); + stream.writeTextElement(QStringLiteral("displayname"), v8event->displayName); + stream.writeTextElement(QStringLiteral("functionname"), v8event->functionName); + if (!v8event->fileName.isEmpty()) { + stream.writeTextElement(QStringLiteral("filename"), v8event->fileName); + stream.writeTextElement(QStringLiteral("line"), QString::number(v8event->line)); + } + stream.writeTextElement(QStringLiteral("totalTime"), QString::number(v8event->totalTime)); + stream.writeTextElement(QStringLiteral("selfTime"), QString::number(v8event->selfTime)); + if (!v8event->v8children.isEmpty()) { + stream.writeStartElement(QStringLiteral("childrenEvents")); + QStringList childrenIndexes; + QStringList childrenTimes; + foreach (const QString &childHash, v8event->v8children.keys()) { + childrenIndexes << QString::number(v8EventIndex(childHash)); + childrenTimes << QString::number(v8event->v8children[childHash]); + } + + stream.writeAttribute(QStringLiteral("list"), childrenIndexes.join(QString(", "))); + stream.writeAttribute(QStringLiteral("childrenTimes"), childrenTimes.join(QString(", "))); + stream.writeEndElement(); + } + stream.writeEndElement(); + } + stream.writeEndElement(); // v8 profiler output + + stream.writeEndElement(); // trace + stream.writeEndDocument(); + + file.close(); + return true; +} + +int QmlProfilerData::v8EventIndex(const QString &hashStr) +{ + if (!d->v8EventHash.contains(hashStr)) { + emit error("Trying to index nonexisting v8 event"); + return -1; + } + return d->v8EventHash.keys().indexOf( hashStr ); +} + +void QmlProfilerData::setState(QmlProfilerData::State state) +{ + // It's not an error, we are continuously calling "AcquiringData" for example + if (d->state == state) + return; + + switch (state) { + case Empty: + // if it's not empty, complain but go on + if (!isEmpty()) + emit error("Invalid qmlprofiler state change (Empty)"); + break; + case AcquiringData: + // we're not supposed to receive new data while processing older data + if (d->state == ProcessingData) + emit error("Invalid qmlprofiler state change (AcquiringData)"); + break; + case ProcessingData: + if (d->state != AcquiringData) + emit error("Invalid qmlprofiler state change (ProcessingData)"); + break; + case Done: + if (d->state != ProcessingData && d->state != Empty) + emit error("Invalid qmlprofiler state change (Done)"); + break; + default: + emit error("Trying to set unknown state in events list"); + break; + } + + d->state = state; + emit stateChanged(); + + // special: if we were done with an empty list, clean internal data and go back to empty + if (d->state == Done && isEmpty()) { + clear(); + } + return; +} + diff --git a/tools/qmlprofiler/qmlprofilerdata.h b/tools/qmlprofiler/qmlprofilerdata.h new file mode 100644 index 0000000000..66209a1830 --- /dev/null +++ b/tools/qmlprofiler/qmlprofilerdata.h @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMLPROFILERDATA_H +#define QMLPROFILERDATA_H + +#include +#include "qmlprofilereventlocation.h" + +#include + +class QmlProfilerDataPrivate; +class QmlProfilerData : public QObject +{ + Q_OBJECT +public: + enum State { + Empty, + AcquiringData, + ProcessingData, + Done + }; + + explicit QmlProfilerData(QObject *parent = 0); + ~QmlProfilerData(); + + static QString getHashStringForQmlEvent(const QmlEventLocation &location, int eventType); + static QString getHashStringForV8Event(const QString &displayName, const QString &function); + static QString qmlRangeTypeAsString(QQmlProfilerService::RangeType typeEnum); + static QString rootEventName(); + static QString rootEventDescription(); + + qint64 traceStartTime() const; + qint64 traceEndTime() const; + + bool isEmpty() const; + +signals: + void error(QString); + void stateChanged(); + void dataReady(); + +public slots: + void clear(); + void setTraceEndTime(qint64 time); + void setTraceStartTime(qint64 time); + void addQmlEvent(QQmlProfilerService::RangeType type, qint64 startTime, qint64 duration, + const QStringList &data, const QmlEventLocation &location); + void addV8Event(int depth, const QString &function, const QString &filename, + int lineNumber, double totalTime, double selfTime); + void addFrameEvent(qint64 time, int framerate, int animationcount); + + void complete(); + bool save(const QString &filename); + +private: + void sortStartTimes(); + int v8EventIndex(const QString &hashStr); + void computeQmlTime(); + void setState(QmlProfilerData::State state); + +private: + QmlProfilerDataPrivate *d; +}; + +#endif // QMLPROFILERDATA_H diff --git a/tools/qmlprofiler/qmlprofilereventlocation.h b/tools/qmlprofiler/qmlprofilereventlocation.h new file mode 100644 index 0000000000..a34b6aba6d --- /dev/null +++ b/tools/qmlprofiler/qmlprofilereventlocation.h @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMLPROFILEREVENTLOCATION_H +#define QMLPROFILEREVENTLOCATION_H + +#include + +struct QmlEventLocation +{ + QmlEventLocation() : line(-1), column(-1) {} + QmlEventLocation(const QString &file, int lineNumber, int columnNumber) + : filename(file), line(lineNumber), column(columnNumber) {} + QString filename; + int line; + int column; +}; + +QT_BEGIN_NAMESPACE +Q_DECLARE_TYPEINFO(QmlEventLocation, Q_MOVABLE_TYPE); +QT_END_NAMESPACE + +#endif // QMLPROFILEREVENTLOCATION_H -- cgit v1.2.3 From a920803631d1788dc563f243a3780899d1ac2af2 Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Tue, 13 Mar 2012 10:27:59 +0100 Subject: Adding a custom easing curve editor to declarative tools Change-Id: Ic8ef77792d74ec99b23d85cd8888e0190acc3e10 Reviewed-by: Thomas Hartmann Reviewed-by: Alessandro Portale --- tools/easingcurveeditor/Button.qml | 177 +++++++ tools/easingcurveeditor/easingcurveeditor.pro | 25 + tools/easingcurveeditor/main.cpp | 55 ++ tools/easingcurveeditor/mainwindow.cpp | 136 +++++ tools/easingcurveeditor/mainwindow.h | 77 +++ tools/easingcurveeditor/pane.ui | 112 ++++ tools/easingcurveeditor/preview.qml | 148 ++++++ tools/easingcurveeditor/properties.ui | 131 +++++ tools/easingcurveeditor/resources.qrc | 6 + tools/easingcurveeditor/segmentproperties.cpp | 128 +++++ tools/easingcurveeditor/segmentproperties.h | 94 ++++ tools/easingcurveeditor/splineeditor.cpp | 714 ++++++++++++++++++++++++++ tools/easingcurveeditor/splineeditor.h | 143 ++++++ tools/tools.pro | 2 +- 14 files changed, 1947 insertions(+), 1 deletion(-) create mode 100644 tools/easingcurveeditor/Button.qml create mode 100644 tools/easingcurveeditor/easingcurveeditor.pro create mode 100644 tools/easingcurveeditor/main.cpp create mode 100644 tools/easingcurveeditor/mainwindow.cpp create mode 100644 tools/easingcurveeditor/mainwindow.h create mode 100644 tools/easingcurveeditor/pane.ui create mode 100644 tools/easingcurveeditor/preview.qml create mode 100644 tools/easingcurveeditor/properties.ui create mode 100644 tools/easingcurveeditor/resources.qrc create mode 100644 tools/easingcurveeditor/segmentproperties.cpp create mode 100644 tools/easingcurveeditor/segmentproperties.h create mode 100644 tools/easingcurveeditor/splineeditor.cpp create mode 100644 tools/easingcurveeditor/splineeditor.h (limited to 'tools') diff --git a/tools/easingcurveeditor/Button.qml b/tools/easingcurveeditor/Button.qml new file mode 100644 index 0000000000..a47809a2d1 --- /dev/null +++ b/tools/easingcurveeditor/Button.qml @@ -0,0 +1,177 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 + +Item { + id: button + + signal clicked + + Rectangle { + id: normalBackground + radius: 4 + anchors.fill: parent + smooth: true + gradient: Gradient { + GradientStop { + position: 0 + color: "#afafaf" + } + + GradientStop { + position: 0.460 + color: "#808080" + } + + GradientStop { + position: 1 + color: "#adadad" + } + } + border.color: "#000000" + } + + + Rectangle { + id: hoveredBackground + x: 2 + y: -8 + radius: 4 + opacity: 0 + gradient: Gradient { + GradientStop { + position: 0 + color: "#cacaca" + } + + GradientStop { + position: 0.460 + color: "#a2a2a2" + } + + GradientStop { + position: 1 + color: "#c8c8c8" + } + } + smooth: true + anchors.fill: parent + border.color: "#000000" + } + + + Rectangle { + id: pressedBackground + x: -8 + y: 2 + radius: 4 + opacity: 0 + gradient: Gradient { + GradientStop { + position: 0 + color: "#8b8b8b" + } + + GradientStop { + position: 0.470 + color: "#626161" + } + + GradientStop { + position: 1 + color: "#8f8e8e" + } + } + smooth: true + anchors.fill: parent + border.color: "#000000" + } + states: [ + State { + name: "hovered" + + PropertyChanges { + target: normalBackground + opacity: 0 + } + + PropertyChanges { + target: hoveredBackground + opacity: 1 + } + }, + State { + name: "pressed" + + PropertyChanges { + target: normalBackground + opacity: 0 + } + + PropertyChanges { + target: pressedBackground + opacity: 1 + } + } + ] + + Text { + color: "#e8e8e8" + text: qsTr("Play") + anchors.fill: parent + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.bold: true + font.pixelSize: 20 + } + + MouseArea { + hoverEnabled: true + anchors.fill: parent + onEntered: button.state = "hovered" + onExited: button.state = "" + onClicked: { + button.state = "pressed" + button.clicked(); + } + } +} diff --git a/tools/easingcurveeditor/easingcurveeditor.pro b/tools/easingcurveeditor/easingcurveeditor.pro new file mode 100644 index 0000000000..0a266d0aee --- /dev/null +++ b/tools/easingcurveeditor/easingcurveeditor.pro @@ -0,0 +1,25 @@ +TEMPLATE = app +DEPENDPATH += . +INCLUDEPATH += . + +QT += declarative quick widgets +CONFIG -= app_bundle + +# Input +SOURCES += main.cpp \ + splineeditor.cpp \ + mainwindow.cpp \ + segmentproperties.cpp + +RESOURCES = $$PWD/resources.qrc + +HEADERS += \ + splineeditor.h \ + mainwindow.h \ + ui_properties.h \ + ui_pane.h \ + segmentproperties.h + +FORMS += \ + properties.ui \ + pane.ui diff --git a/tools/easingcurveeditor/main.cpp b/tools/easingcurveeditor/main.cpp new file mode 100644 index 0000000000..05e8ace53a --- /dev/null +++ b/tools/easingcurveeditor/main.cpp @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + + +int main(int argc, char ** argv) +{ + QApplication app(argc, argv); + + MainWindow mainWindow; + mainWindow.show(); + mainWindow.showQuickView(); + + return app.exec(); +} diff --git a/tools/easingcurveeditor/mainwindow.cpp b/tools/easingcurveeditor/mainwindow.cpp new file mode 100644 index 0000000000..569b74ae29 --- /dev/null +++ b/tools/easingcurveeditor/mainwindow.cpp @@ -0,0 +1,136 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "mainwindow.h" +#include "splineeditor.h" +#include +#include +#include +#include +#include +#include + +MainWindow::MainWindow(QWidget *parent) : + QMainWindow(parent) +{ + SplineEditor *splineEditor = new SplineEditor(this); + + QWidget *mainWidget = new QWidget(this); + + setCentralWidget(mainWidget); + + QHBoxLayout *hboxLayout = new QHBoxLayout(mainWidget); + QVBoxLayout *vboxLayout = new QVBoxLayout(); + + mainWidget->setLayout(hboxLayout); + hboxLayout->addLayout(vboxLayout); + + QWidget *propertyWidget = new QWidget(this); + ui_properties.setupUi(propertyWidget); + + ui_properties.spinBox->setMinimum(50); + ui_properties.spinBox->setMaximum(10000); + ui_properties.spinBox->setValue(500); + + hboxLayout->addWidget(propertyWidget); + + m_placeholder = new QWidget(this); + + m_placeholder->setFixedSize(quickView.size()); + + vboxLayout->addWidget(splineEditor); + vboxLayout->addWidget(m_placeholder); + + ui_properties.plainTextEdit->setPlainText(splineEditor->generateCode()); + connect(splineEditor, SIGNAL(easingCurveCodeChanged(QString)), ui_properties.plainTextEdit, SLOT(setPlainText(QString))); + + quickView.rootContext()->setContextProperty(QLatin1String("spinBox"), ui_properties.spinBox); + + foreach (const QString &name, splineEditor->presetNames()) + ui_properties.comboBox->addItem(name); + + connect(ui_properties.comboBox, SIGNAL(currentIndexChanged(QString)), splineEditor, SLOT(setPreset(QString))); + + splineEditor->setPreset(ui_properties.comboBox->currentText()); + + QVBoxLayout *groupBoxLayout = new QVBoxLayout(ui_properties.groupBox); + groupBoxLayout->setMargin(0); + ui_properties.groupBox->setLayout(groupBoxLayout); + + groupBoxLayout->addWidget(splineEditor->pointListWidget()); + m_splineEditor = splineEditor; + connect(ui_properties.plainTextEdit, SIGNAL(textChanged()), this, SLOT(textEditTextChanged())); + initQml(); +} + +void MainWindow::showQuickView() +{ + const int margin = 16; + quickView.move(pos() + QPoint(0, frameGeometry().height() + margin)); + + quickView.raise(); + quickView.show(); +} + +void MainWindow::textEditTextChanged() +{ + m_splineEditor->setEasingCurve(ui_properties.plainTextEdit->toPlainText().trimmed()); +} + +void MainWindow::moveEvent(QMoveEvent *event) +{ + QMainWindow::moveEvent(event); + showQuickView(); +} + +void MainWindow::resizeEvent(QResizeEvent *event) +{ + QMainWindow::resizeEvent(event); + showQuickView(); +} + +void MainWindow::initQml() +{ + quickView.setWindowFlags(Qt::FramelessWindowHint); + quickView.rootContext()->setContextProperty(QLatin1String("editor"), m_splineEditor); + quickView.setSource(QUrl("qrc:/preview.qml")); + quickView.show(); +} diff --git a/tools/easingcurveeditor/mainwindow.h b/tools/easingcurveeditor/mainwindow.h new file mode 100644 index 0000000000..44591d33cc --- /dev/null +++ b/tools/easingcurveeditor/mainwindow.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include "ui_properties.h" + +class SplineEditor; + +class MainWindow : public QMainWindow +{ + Q_OBJECT +public: + explicit MainWindow(QWidget *parent = 0); + + void showQuickView(); + +signals: + +public slots: + void textEditTextChanged(); + +protected: + virtual void moveEvent(QMoveEvent *event); + virtual void resizeEvent(QResizeEvent *event); + void initQml(); + +private: + QQuickView quickView; + QWidget *m_placeholder; + Ui_Properties ui_properties; + SplineEditor *m_splineEditor; + +}; + +#endif // MAINWINDOW_H diff --git a/tools/easingcurveeditor/pane.ui b/tools/easingcurveeditor/pane.ui new file mode 100644 index 0000000000..1500589192 --- /dev/null +++ b/tools/easingcurveeditor/pane.ui @@ -0,0 +1,112 @@ + + + Pane + + + + 0 + 0 + 416 + 47 + + + + + 0 + 0 + + + + Form + + + + + + + 75 + true + + + + p1 + + + + + + + + 4 + + + 2 + + + + + x + + + + + + + 4 + + + 0.010000000000000 + + + + + + + y + + + + + + + 4 + + + -10.000000000000000 + + + 10.000000000000000 + + + 0.010000000000000 + + + + + + + + + + smooth + + + + + + + Qt::Horizontal + + + + 99 + 10 + + + + + + + + + diff --git a/tools/easingcurveeditor/preview.qml b/tools/easingcurveeditor/preview.qml new file mode 100644 index 0000000000..61f4862e76 --- /dev/null +++ b/tools/easingcurveeditor/preview.qml @@ -0,0 +1,148 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 + +Item { + id: root + width: 800 + height: 100 + + Rectangle { + gradient: Gradient { + GradientStop { + position: 0 + color: "#aaa7a7" + } + + GradientStop { + position: 0.340 + color: "#a4a4a4" + } + + GradientStop { + position: 1 + color: "#6b6b6b" + } + } + anchors.fill: parent + } + + Button { + id: button + + x: 19 + y: 20 + width: 133 + height: 61 + onClicked: { + if (root.state ==="") + root.state = "moved"; + else + root.state = ""; + } + } + + Rectangle { + id: groove + x: 163 + y: 20 + width: 622 + height: 61 + color: "#919191" + radius: 4 + border.color: "#adadad" + + Rectangle { + id: rectangle + x: 9 + y: 9 + width: 46 + height: 46 + color: "#3045b7" + radius: 4 + border.width: 2 + smooth: true + border.color: "#9ea0bb" + anchors.bottomMargin: 6 + anchors.topMargin: 9 + anchors.top: parent.top + anchors.bottom: parent.bottom + } + } + states: [ + State { + name: "moved" + + PropertyChanges { + target: rectangle + x: 567 + y: 9 + anchors.bottomMargin: 6 + anchors.topMargin: 9 + } + } + ] + + transitions: [ + Transition { + from: "" + to: "moved" + SequentialAnimation { + PropertyAnimation { + easing: editor.easingCurve + property: "x" + duration: spinBox.value + } + } + }, + Transition { + from: "moved" + to: "" + PropertyAnimation { + easing: editor.easingCurve + property: "x" + duration: spinBox.value + } + + } + ] +} diff --git a/tools/easingcurveeditor/properties.ui b/tools/easingcurveeditor/properties.ui new file mode 100644 index 0000000000..af96e9c7a1 --- /dev/null +++ b/tools/easingcurveeditor/properties.ui @@ -0,0 +1,131 @@ + + + Properties + + + + 0 + 0 + 487 + 627 + + + + + 0 + 0 + + + + Form + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 13 + + + + + + + + Duration + + + + + + + + + + Code + + + + + + + + 16777215 + 128 + + + + false + + + + + + + + + + Presets + + + + + + + + 0 + 0 + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + + 0 + 0 + + + + + 400 + 0 + + + + Control Points + + + true + + + + + + + + diff --git a/tools/easingcurveeditor/resources.qrc b/tools/easingcurveeditor/resources.qrc new file mode 100644 index 0000000000..c184af4662 --- /dev/null +++ b/tools/easingcurveeditor/resources.qrc @@ -0,0 +1,6 @@ + + + preview.qml + Button.qml + + diff --git a/tools/easingcurveeditor/segmentproperties.cpp b/tools/easingcurveeditor/segmentproperties.cpp new file mode 100644 index 0000000000..886d4636aa --- /dev/null +++ b/tools/easingcurveeditor/segmentproperties.cpp @@ -0,0 +1,128 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "segmentproperties.h" +#include "splineeditor.h" + +SegmentProperties::SegmentProperties(QWidget *parent) : + QWidget(parent), m_splineEditor(0), m_blockSignals(false) +{ + QVBoxLayout *layout = new QVBoxLayout(this); + layout->setMargin(0); + layout->setSpacing(2); + setLayout(layout); + { + QWidget *widget = new QWidget(this); + m_ui_pane_c1.setupUi(widget); + m_ui_pane_c1.label->setText("c1"); + m_ui_pane_c1.smooth->setVisible(false); + layout->addWidget(widget); + + connect(m_ui_pane_c1.p1_x, SIGNAL(valueChanged(double)), this, SLOT(c1Updated())); + connect(m_ui_pane_c1.p1_y, SIGNAL(valueChanged(double)), this, SLOT(c1Updated())); + } + { + QWidget *widget = new QWidget(this); + m_ui_pane_c2.setupUi(widget); + m_ui_pane_c2.label->setText("c2"); + m_ui_pane_c2.smooth->setVisible(false); + layout->addWidget(widget); + + connect(m_ui_pane_c2.p1_x, SIGNAL(valueChanged(double)), this, SLOT(c2Updated())); + connect(m_ui_pane_c2.p1_y, SIGNAL(valueChanged(double)), this, SLOT(c2Updated())); + } + { + QWidget *widget = new QWidget(this); + m_ui_pane_p.setupUi(widget); + m_ui_pane_p.label->setText("p1"); + layout->addWidget(widget); + + connect(m_ui_pane_p.smooth, SIGNAL(toggled(bool)), this, SLOT(pUpdated())); + connect(m_ui_pane_p.p1_x, SIGNAL(valueChanged(double)), this, SLOT(pUpdated())); + connect(m_ui_pane_p.p1_y, SIGNAL(valueChanged(double)), this, SLOT(pUpdated())); + } +} + +void SegmentProperties::c1Updated() +{ + if (m_splineEditor && !m_blockSignals) { + QPointF c1(m_ui_pane_c1.p1_x->value(), m_ui_pane_c1.p1_y->value()); + m_splineEditor->setControlPoint(m_segment * 3, c1); + } +} + +void SegmentProperties::c2Updated() +{ + if (m_splineEditor && !m_blockSignals) { + QPointF c2(m_ui_pane_c2.p1_x->value(), m_ui_pane_c2.p1_y->value()); + m_splineEditor->setControlPoint(m_segment * 3 + 1, c2); + } +} + +void SegmentProperties::pUpdated() +{ + if (m_splineEditor && !m_blockSignals) { + QPointF p(m_ui_pane_p.p1_x->value(), m_ui_pane_p.p1_y->value()); + bool smooth = m_ui_pane_p.smooth->isChecked(); + m_splineEditor->setControlPoint(m_segment * 3 + 2, p); + m_splineEditor->setSmooth(m_segment, smooth); + } +} + +void SegmentProperties::invalidate() +{ + m_blockSignals = true; + + m_ui_pane_p.label->setText(QLatin1String("p") + QString::number(m_segment)); + m_ui_pane_p.smooth->setChecked(m_smooth); + m_ui_pane_p.smooth->parentWidget()->setEnabled(!m_last); + + m_ui_pane_c1.p1_x->setValue(m_points.at(0).x()); + m_ui_pane_c1.p1_y->setValue(m_points.at(0).y()); + + m_ui_pane_c2.p1_x->setValue(m_points.at(1).x()); + m_ui_pane_c2.p1_y->setValue(m_points.at(1).y()); + + m_ui_pane_p.p1_x->setValue(m_points.at(2).x()); + m_ui_pane_p.p1_y->setValue(m_points.at(2).y()); + + m_blockSignals = false; +} diff --git a/tools/easingcurveeditor/segmentproperties.h b/tools/easingcurveeditor/segmentproperties.h new file mode 100644 index 0000000000..4d86be3882 --- /dev/null +++ b/tools/easingcurveeditor/segmentproperties.h @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef SEGMENTPROPERTIES_H +#define SEGMENTPROPERTIES_H + +#include +#include + +class SplineEditor; + +class SegmentProperties : public QWidget +{ + Q_OBJECT +public: + explicit SegmentProperties(QWidget *parent = 0); + void setSplineEditor(SplineEditor *splineEditor) + { + m_splineEditor = splineEditor; + } + + void setSegment(int segment, QVector points, bool smooth, bool last) + { + m_segment = segment; + m_points = points; + m_smooth = smooth; + m_last = last; + invalidate(); + } + +signals: + +public slots: + +private slots: + void c1Updated(); + void c2Updated(); + void pUpdated(); + +private: + void invalidate(); + + Ui_Pane m_ui_pane_c1; + Ui_Pane m_ui_pane_c2; + Ui_Pane m_ui_pane_p; + + SplineEditor *m_splineEditor; + QVector m_points; + int m_segment; + bool m_smooth; + bool m_last; + + bool m_blockSignals; +}; + +#endif // SEGMENTPROPERTIES_H diff --git a/tools/easingcurveeditor/splineeditor.cpp b/tools/easingcurveeditor/splineeditor.cpp new file mode 100644 index 0000000000..dbd32dd63a --- /dev/null +++ b/tools/easingcurveeditor/splineeditor.cpp @@ -0,0 +1,714 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "splineeditor.h" + +#include +#include +#include +#include +#include +#include + +const int canvasWidth = 640; +const int canvasHeight = 320; + +const int canvasMargin = 160; + +SplineEditor::SplineEditor(QWidget *parent) : + QWidget(parent), m_pointListWidget(0), m_block(false) +{ + setFixedSize(canvasWidth + canvasMargin * 2, canvasHeight + canvasMargin * 2); + + m_controlPoints.append(QPointF(0.4, 0.075)); + m_controlPoints.append(QPointF(0.45,0.24)); + m_controlPoints.append(QPointF(0.5,0.5)); + + m_controlPoints.append(QPointF(0.55,0.76)); + m_controlPoints.append(QPointF(0.7,0.9)); + m_controlPoints.append(QPointF(1.0, 1.0)); + + m_numberOfSegments = 2; + + m_activeControlPoint = -1; + + m_mouseDrag = false; + + m_pointContextMenu = new QMenu(this); + m_deleteAction = new QAction(tr("Delete point"), m_pointContextMenu); + m_smoothAction = new QAction(tr("Smooth point"), m_pointContextMenu); + m_cornerAction = new QAction(tr("Corner point"), m_pointContextMenu); + + m_smoothAction->setCheckable(true); + + m_pointContextMenu->addAction(m_deleteAction); + m_pointContextMenu->addAction(m_smoothAction); + m_pointContextMenu->addAction(m_cornerAction); + + m_curveContextMenu = new QMenu(this); + + m_addPoint = new QAction(tr("Add point"), m_pointContextMenu); + + m_curveContextMenu->addAction(m_addPoint); + + initPresets(); + + invalidateSmoothList(); +} + +static inline QPointF mapToCanvas(const QPointF &point) +{ + return QPointF(point.x() * canvasWidth + canvasMargin, + canvasHeight - point.y() * canvasHeight + canvasMargin); +} + +static inline QPointF mapFromCanvas(const QPointF &point) +{ + return QPointF((point.x() - canvasMargin) / canvasWidth , + 1 - (point.y() - canvasMargin) / canvasHeight); +} + +static inline void paintControlPoint(const QPointF &controlPoint, QPainter *painter, bool edit, + bool realPoint, bool active, bool smooth) +{ + int pointSize = 4; + + if (active) + painter->setBrush(QColor(140, 140, 240, 255)); + else + painter->setBrush(QColor(120, 120, 220, 255)); + + if (realPoint) { + pointSize = 6; + painter->setBrush(QColor(80, 80, 210, 150)); + } + + painter->setPen(QColor(50, 50, 50, 140)); + + if (!edit) + painter->setBrush(QColor(160, 80, 80, 250)); + + if (smooth) { + painter->drawEllipse(QRectF(mapToCanvas(controlPoint).x() - pointSize + 0.5, + mapToCanvas(controlPoint).y() - pointSize + 0.5, + pointSize * 2, pointSize * 2)); + } else { + painter->drawRect(QRectF(mapToCanvas(controlPoint).x() - pointSize + 0.5, + mapToCanvas(controlPoint).y() - pointSize + 0.5, + pointSize * 2, pointSize * 2)); + } +} + +static inline bool indexIsRealPoint(int i) +{ + return !((i + 1) % 3); +} + +static inline int pointForControlPoint(int i) +{ + if ((i % 3) == 0) + return i - 1; + + if ((i % 3) == 1) + return i + 1; + + return i; +} + +void drawCleanLine(QPainter *painter, const QPoint p1, QPoint p2) +{ + painter->drawLine(p1 + QPointF(0.5 , 0.5), p2 + QPointF(0.5, 0.5)); +} + +void SplineEditor::paintEvent(QPaintEvent *) +{ + QPainter painter(this); + + QPen pen(Qt::black); + pen.setWidth(1); + painter.fillRect(0,0,width() - 1, height() - 1, QBrush(Qt::white)); + painter.drawRect(0,0,width() - 1, height() - 1); + + painter.setRenderHint(QPainter::Antialiasing); + + pen = QPen(Qt::gray); + pen.setWidth(1); + pen.setStyle(Qt::DashLine); + painter.setPen(pen); + drawCleanLine(&painter,mapToCanvas(QPoint(0, 0)).toPoint(), mapToCanvas(QPoint(1, 0)).toPoint()); + drawCleanLine(&painter,mapToCanvas(QPoint(0, 1)).toPoint(), mapToCanvas(QPoint(1, 1)).toPoint()); + + for (int i = 0; i < m_numberOfSegments; i++) { + QPainterPath path; + QPointF p0; + + if (i == 0) + p0 = mapToCanvas(QPointF(0.0, 0.0)); + else + p0 = mapToCanvas(m_controlPoints.at(i * 3 - 1)); + + path.moveTo(p0); + + QPointF p1 = mapToCanvas(m_controlPoints.at(i * 3)); + QPointF p2 = mapToCanvas(m_controlPoints.at(i * 3 + 1)); + QPointF p3 = mapToCanvas(m_controlPoints.at(i * 3 + 2)); + path.cubicTo(p1, p2, p3); + painter.strokePath(path, QPen(QBrush(Qt::black), 2)); + + QPen pen(Qt::black); + pen.setWidth(1); + pen.setStyle(Qt::DashLine); + painter.setPen(pen); + painter.drawLine(p0, p1); + painter.drawLine(p3, p2); + } + + paintControlPoint(QPointF(0.0, 0.0), &painter, false, true, false, false); + paintControlPoint(QPointF(1.0, 1.0), &painter, false, true, false, false); + + for (int i = 0; i < m_controlPoints.count() - 1; ++i) + paintControlPoint(m_controlPoints.at(i), + &painter, + true, + indexIsRealPoint(i), + i == m_activeControlPoint, + isControlPointSmooth(i)); +} + +void SplineEditor::mousePressEvent(QMouseEvent *e) +{ + if (e->button() == Qt::LeftButton) { + m_activeControlPoint = findControlPoint(e->pos()); + + if (m_activeControlPoint != -1) { + mouseMoveEvent(e); + } + m_mousePress = e->pos(); + e->accept(); + } +} + +void SplineEditor::mouseReleaseEvent(QMouseEvent *e) +{ + if (e->button() == Qt::LeftButton) { + m_activeControlPoint = -1; + + m_mouseDrag = false; + e->accept(); + } +} + +void SplineEditor::contextMenuEvent(QContextMenuEvent *e) +{ + int index = findControlPoint(e->pos()); + + if (index > 0 && indexIsRealPoint(index)) { + m_smoothAction->setChecked(isControlPointSmooth(index)); + QAction* action = m_pointContextMenu->exec(e->globalPos()); + if (action == m_deleteAction) + deletePoint(index); + else if (action == m_smoothAction) + smoothPoint(index); + else if (action == m_cornerAction) + cornerPoint(index); + } else { + QAction* action = m_curveContextMenu->exec(e->globalPos()); + if (action == m_addPoint) + addPoint(e->pos()); + } +} + +void SplineEditor::invalidate() +{ + QEasingCurve easingCurve(QEasingCurve::BezierSpline); + + for (int i = 0; i < m_numberOfSegments; ++i) { + easingCurve.addCubicBezierSegment(m_controlPoints.at(i * 3), + m_controlPoints.at(i * 3 + 1), + m_controlPoints.at(i * 3 + 2)); + } + setEasingCurve(easingCurve); + invalidateSegmentProperties(); +} + +void SplineEditor::invalidateSmoothList() +{ + m_smoothList.clear(); + + for (int i = 0; i < (m_numberOfSegments - 1); ++i) + m_smoothList.append(isSmooth(i * 3 + 2)); + +} + +void SplineEditor::invalidateSegmentProperties() +{ + for (int i = 0; i < m_numberOfSegments; ++i) { + SegmentProperties *segmentProperties = m_segmentProperties.at(i); + bool smooth = false; + if (i < (m_numberOfSegments - 1)) { + smooth = m_smoothList.at(i); + } + segmentProperties->setSegment(i, m_controlPoints.mid(i * 3, 3), smooth, i == (m_numberOfSegments - 1)); + } +} + +QHash SplineEditor::presets() const +{ + return m_presets; +} + +QString SplineEditor::generateCode() +{ + QString s = QLatin1String("["); + foreach (const QPointF &point, m_controlPoints) { + s += QString::number(point.x(), 'g', 2) + "," + QString::number(point.y(), 'g', 3) + ","; + } + s.chop(1); //removing last "," + s += "]"; + + return s; +} + +QStringList SplineEditor::presetNames() const +{ + return m_presets.keys(); +} + +QWidget *SplineEditor::pointListWidget() +{ + if (!m_pointListWidget) { + setupPointListWidget(); + } + + return m_pointListWidget; +} + +int SplineEditor::findControlPoint(const QPoint &point) +{ + int pointIndex = -1; + qreal distance = -1; + for (int i = 0; iisChecked()) { + + QPointF before = QPointF(0,0); + if (index > 3) + before = m_controlPoints.at(index - 3); + + QPointF after = QPointF(1.0, 1.0); + if ((index + 3) < m_controlPoints.count()) + after = m_controlPoints.at(index + 3); + + QPointF tangent = (after - before) / 6; + + QPointF thisPoint = m_controlPoints.at(index); + + if (index > 0) + m_controlPoints[index - 1] = thisPoint - tangent; + + if (index + 1 < m_controlPoints.count()) + m_controlPoints[index + 1] = thisPoint + tangent; + + m_smoothList[index / 3] = true; + } else { + m_smoothList[index / 3] = false; + } + invalidate(); + update(); +} + +void SplineEditor::cornerPoint(int index) +{ + QPointF before = QPointF(0,0); + if (index > 3) + before = m_controlPoints.at(index - 3); + + QPointF after = QPointF(1.0, 1.0); + if ((index + 3) < m_controlPoints.count()) + after = m_controlPoints.at(index + 3); + + QPointF thisPoint = m_controlPoints.at(index); + + if (index > 0) + m_controlPoints[index - 1] = (before - thisPoint) / 3 + thisPoint; + + if (index + 1 < m_controlPoints.count()) + m_controlPoints[index + 1] = (after - thisPoint) / 3 + thisPoint; + + m_smoothList[(index) / 3] = false; + invalidate(); +} + +void SplineEditor::deletePoint(int index) +{ + m_controlPoints.remove(index - 1, 3); + m_numberOfSegments--; + + invalidateSmoothList(); + setupPointListWidget(); + invalidate(); +} + +void SplineEditor::addPoint(const QPointF point) +{ + QPointF newPos = mapFromCanvas(point); + int splitIndex = 0; + for (int i=0; i < m_controlPoints.size() - 1; ++i) { + if (indexIsRealPoint(i) && m_controlPoints.at(i).x() > newPos.x()) { + break; + } else if (indexIsRealPoint(i)) + splitIndex = i; + } + QPointF before = QPointF(0,0); + if (splitIndex > 0) + before = m_controlPoints.at(splitIndex); + + QPointF after = QPointF(1.0, 1.0); + if ((splitIndex + 3) < m_controlPoints.count()) + after = m_controlPoints.at(splitIndex + 3); + + if (splitIndex > 0) { + m_controlPoints.insert(splitIndex + 2, (newPos + after) / 2); + m_controlPoints.insert(splitIndex + 2, newPos); + m_controlPoints.insert(splitIndex + 2, (newPos + before) / 2); + } else { + m_controlPoints.insert(splitIndex + 1, (newPos + after) / 2); + m_controlPoints.insert(splitIndex + 1, newPos); + m_controlPoints.insert(splitIndex + 1, (newPos + before) / 2); + } + m_numberOfSegments++; + + invalidateSmoothList(); + setupPointListWidget(); + invalidate(); +} + +void SplineEditor::initPresets() +{ + const QPointF endPoint(1.0, 1.0); + { + QEasingCurve easingCurve(QEasingCurve::BezierSpline); + easingCurve.addCubicBezierSegment(QPointF(0.4, 0.075), QPointF(0.45, 0.24), QPointF(0.5, 0.5)); + easingCurve.addCubicBezierSegment(QPointF(0.55, 0.76), QPointF(0.7, 0.9), endPoint); + m_presets.insert(tr("Standard Easing"), easingCurve); + } + + { + QEasingCurve easingCurve(QEasingCurve::BezierSpline); + easingCurve.addCubicBezierSegment(QPointF(0.43, 0.0025), QPointF(0.65, 1), endPoint); + m_presets.insert(tr("Simple"), easingCurve); + } + + { + QEasingCurve easingCurve(QEasingCurve::BezierSpline); + easingCurve.addCubicBezierSegment(QPointF(0.43, 0.0025), QPointF(0.38, 0.51), QPointF(0.57, 0.99)); + easingCurve.addCubicBezierSegment(QPointF(0.8, 0.69), QPointF(0.65, 1), endPoint); + m_presets.insert(tr("Simple Bounce"), easingCurve); + } + + { + QEasingCurve easingCurve(QEasingCurve::BezierSpline); + easingCurve.addCubicBezierSegment(QPointF(0.4, 0.075), QPointF(0.64, -0.0025), QPointF(0.74, 0.23)); + easingCurve.addCubicBezierSegment(QPointF(0.84, 0.46), QPointF(0.91, 0.77), endPoint); + m_presets.insert(tr("Slow in fast out"), easingCurve); + } + + { + QEasingCurve easingCurve(QEasingCurve::BezierSpline); + easingCurve.addCubicBezierSegment(QPointF(0.43, 0.0025), QPointF(0.47, 0.51), QPointF(0.59, 0.94)); + easingCurve.addCubicBezierSegment(QPointF(0.84, 0.95), QPointF( 0.99, 0.94), endPoint); + m_presets.insert(tr("Snapping"), easingCurve); + } + + { + QEasingCurve easingCurve(QEasingCurve::BezierSpline); + easingCurve.addCubicBezierSegment(QPointF( 0.38, 0.35),QPointF(0.38, 0.7), QPointF(0.45, 0.99)); + easingCurve.addCubicBezierSegment(QPointF(0.48, 0.66), QPointF(0.62, 0.62), QPointF(0.66, 0.99)); + easingCurve.addCubicBezierSegment(QPointF(0.69, 0.76), QPointF(0.77, 0.76), QPointF(0.79, 0.99)); + easingCurve.addCubicBezierSegment(QPointF(0.83, 0.91), QPointF(0.87, 0.92), QPointF(0.91, 0.99)); + easingCurve.addCubicBezierSegment(QPointF(0.95, 0.95), QPointF(0.97, 0.94), endPoint); + m_presets.insert(tr("Complex Bounce"), easingCurve); + } + + { + QEasingCurve easingCurve4(QEasingCurve::BezierSpline); + easingCurve4.addCubicBezierSegment(QPointF(0.12, -0.12),QPointF(0.23, -0.19), QPointF( 0.35, -0.09)); + easingCurve4.addCubicBezierSegment(QPointF(0.47, 0.005), QPointF(0.52, 1), QPointF(0.62, 1.1)); + easingCurve4.addCubicBezierSegment(QPointF(0.73, 1.2), QPointF(0.91,1 ), endPoint); + m_presets.insert(tr("Overshoot"), easingCurve4); + } +} + +void SplineEditor::setupPointListWidget() +{ + if (!m_pointListWidget) + m_pointListWidget = new QScrollArea(this); + + if (m_pointListWidget->widget()) + delete m_pointListWidget->widget(); + + m_pointListWidget->setFrameStyle(QFrame::NoFrame); + m_pointListWidget->setWidgetResizable(true); + m_pointListWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + m_pointListWidget->setWidget(new QWidget(m_pointListWidget)); + QVBoxLayout *layout = new QVBoxLayout(m_pointListWidget->widget()); + layout->setMargin(0); + layout->setSpacing(2); + m_pointListWidget->widget()->setLayout(layout); + + m_segmentProperties.clear(); + + { //implicit 0,0 + QWidget *widget = new QWidget(m_pointListWidget->widget()); + Ui_Pane pane; + pane.setupUi(widget); + pane.p1_x->setValue(0); + pane.p1_y->setValue(0); + layout->addWidget(widget); + pane.label->setText("p0"); + widget->setEnabled(false); + } + + for (int i = 0; i < m_numberOfSegments; ++i) { + SegmentProperties *segmentProperties = new SegmentProperties(m_pointListWidget->widget()); + layout->addWidget(segmentProperties); + bool smooth = false; + if (i < (m_numberOfSegments - 1)) { + smooth = m_smoothList.at(i); + } + segmentProperties->setSegment(i, m_controlPoints.mid(i * 3, 3), smooth, i == (m_numberOfSegments - 1)); + segmentProperties->setSplineEditor(this); + m_segmentProperties << segmentProperties; + } + layout->addSpacerItem(new QSpacerItem(10, 10, QSizePolicy::Expanding, QSizePolicy::Expanding)); + + m_pointListWidget->viewport()->show(); + m_pointListWidget->viewport()->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + m_pointListWidget->show(); +} + +bool SplineEditor::isControlPointSmooth(int i) const +{ + if (i == 0) + return false; + + if (i == m_controlPoints.count() - 1) + return false; + + if (m_numberOfSegments == 1) + return false; + + int index = pointForControlPoint(i); + + if (index == 0) + return false; + + if (index == m_controlPoints.count() - 1) + return false; + + return m_smoothList.at(index / 3); +} + +QPointF limitToCanvas(const QPointF point) +{ + qreal left = -qreal( canvasMargin) / qreal(canvasWidth); + qreal width = 1.0 - 2.0 * left; + + qreal top = -qreal( canvasMargin) / qreal(canvasHeight); + qreal height = 1.0 - 2.0 * top; + + QPointF p = point; + QRectF r(left, top, width, height); + + if (p.x() > r.right()) { + p.setX(r.right()); + } + if (p.x() < r.left()) { + p.setX(r.left()); + } + if (p.y() < r.top()) { + p.setY(r.top()); + } + if (p.y() > r.bottom()) { + p.setY(r.bottom()); + } + return p; +} + +void SplineEditor::mouseMoveEvent(QMouseEvent *e) +{ + // If we've moved more then 25 pixels, assume user is dragging + if (!m_mouseDrag && QPoint(m_mousePress - e->pos()).manhattanLength() > qApp->startDragDistance()) + m_mouseDrag = true; + + QPointF p = mapFromCanvas(e->pos()); + + if (m_mouseDrag && m_activeControlPoint >= 0 && m_activeControlPoint < m_controlPoints.size()) { + p = limitToCanvas(p); + if (indexIsRealPoint(m_activeControlPoint)) { + //move also the tangents + QPointF targetPoint = p; + QPointF distance = targetPoint - m_controlPoints[m_activeControlPoint]; + m_controlPoints[m_activeControlPoint] = targetPoint; + m_controlPoints[m_activeControlPoint - 1] += distance; + m_controlPoints[m_activeControlPoint + 1] += distance; + } else { + if (!isControlPointSmooth(m_activeControlPoint)) { + m_controlPoints[m_activeControlPoint] = p; + } else { + QPointF targetPoint = p; + QPointF distance = targetPoint - m_controlPoints[m_activeControlPoint]; + m_controlPoints[m_activeControlPoint] = p; + + if ((m_activeControlPoint > 1) && (m_activeControlPoint % 3) == 0) { //right control point + m_controlPoints[m_activeControlPoint - 2] -= distance; + } else if ((m_activeControlPoint < (m_controlPoints.count() - 2)) //left control point + && (m_activeControlPoint % 3) == 1) { + m_controlPoints[m_activeControlPoint + 2] -= distance; + } + } + } + invalidate(); + } +} + +void SplineEditor::setEasingCurve(const QEasingCurve &easingCurve) +{ + if (m_easingCurve == easingCurve) + return; + m_block = true; + m_easingCurve = easingCurve; + m_controlPoints = m_easingCurve.cubicBezierSpline().toVector(); + m_numberOfSegments = m_controlPoints.count() / 3; + update(); + emit easingCurveChanged(); + + const QString code = generateCode(); + emit easingCurveCodeChanged(code); + + m_block = false; +} + +void SplineEditor::setPreset(const QString &name) +{ + setEasingCurve(m_presets.value(name)); + invalidateSmoothList(); + setupPointListWidget(); +} + +void SplineEditor::setEasingCurve(const QString &code) +{ + if (m_block) + return; + if (code.left(1) == QLatin1String("[") && code.right(1) == QLatin1String("]")) { + QString cleanCode = code; + cleanCode.remove(0, 1); + cleanCode.chop(1); + const QStringList stringList = cleanCode.split(QLatin1Char(','), QString::SkipEmptyParts); + if (stringList.count() >= 6 && (stringList.count() % 6 == 0)) { + QList realList; + foreach (const QString &string, stringList) { + bool ok; + realList.append(string.toDouble(&ok)); + if (!ok) + return; + } + QList points; + for (int i = 0; i < realList.count() / 2; ++i) + points.append(QPointF(realList.at(i * 2), realList.at(i * 2 + 1))); + if (points.last() == QPointF(1.0, 1.0)) { + QEasingCurve easingCurve(QEasingCurve::BezierSpline); + + for (int i = 0; i < points.count() / 3; ++i) { + easingCurve.addCubicBezierSegment(points.at(i * 3), + points.at(i * 3 + 1), + points.at(i * 3 + 2)); + } + setEasingCurve(easingCurve); + invalidateSmoothList(); + setupPointListWidget(); + } + } + } +} diff --git a/tools/easingcurveeditor/splineeditor.h b/tools/easingcurveeditor/splineeditor.h new file mode 100644 index 0000000000..81f2dfd1ce --- /dev/null +++ b/tools/easingcurveeditor/splineeditor.h @@ -0,0 +1,143 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef SPLINEEDITOR_H +#define SPLINEEDITOR_H + +#include +#include +#include +#include + +#include +#include + +class SegmentProperties; + +class SplineEditor : public QWidget +{ + Q_OBJECT + + Q_PROPERTY(QEasingCurve easingCurve READ easingCurve WRITE setEasingCurve NOTIFY easingCurveChanged); + +public: + explicit SplineEditor(QWidget *parent = 0); + QString generateCode(); + QStringList presetNames() const; + QWidget *pointListWidget(); + + void setControlPoint(int index, const QPointF &point) + { + m_controlPoints[index] = point; + update(); + } + + void setSmooth(int index, bool smooth) + { + m_smoothAction->setChecked(smooth); + smoothPoint(index * 3 + 2); + //update(); + } + +signals: + void easingCurveChanged(); + void easingCurveCodeChanged(const QString &code); + + +public slots: + void setEasingCurve(const QEasingCurve &easingCurve); + void setPreset(const QString &name); + void setEasingCurve(const QString &code); + +protected: + void paintEvent(QPaintEvent *); + void mousePressEvent(QMouseEvent *); + void mouseMoveEvent(QMouseEvent *); + void mouseReleaseEvent(QMouseEvent *); + void contextMenuEvent(QContextMenuEvent *); + + void invalidate(); + void invalidateSmoothList(); + void invalidateSegmentProperties(); + + QEasingCurve easingCurve() const + { return m_easingCurve; } + + QHash presets() const; + +private: + int findControlPoint(const QPoint &point); + bool isSmooth(int i) const; + + void smoothPoint( int index); + void cornerPoint( int index); + void deletePoint(int index); + void addPoint(const QPointF point); + + void initPresets(); + + void setupPointListWidget(); + + bool isControlPointSmooth(int i) const; + + QEasingCurve m_easingCurve; + QVector m_controlPoints; + QVector m_smoothList; + int m_numberOfSegments; + int m_activeControlPoint; + bool m_mouseDrag; + QPoint m_mousePress; + QHash m_presets; + + QMenu *m_pointContextMenu; + QMenu *m_curveContextMenu; + QAction *m_deleteAction; + QAction *m_smoothAction; + QAction *m_cornerAction; + QAction *m_addPoint; + + QScrollArea *m_pointListWidget; + + QList m_segmentProperties; + bool m_block; +}; + +#endif // SPLINEEDITOR_H diff --git a/tools/tools.pro b/tools/tools.pro index 34e90ed816..186c7e9c3b 100644 --- a/tools/tools.pro +++ b/tools/tools.pro @@ -1,5 +1,5 @@ TEMPLATE = subdirs -SUBDIRS += qmlscene qmlplugindump qmlmin qmleasing qmlprofiler +SUBDIRS += qmlscene qmlplugindump qmlmin qmleasing qmlprofiler easingcurveeditor contains(QT_CONFIG, qmltest): SUBDIRS += qmltestrunner -- cgit v1.2.3