aboutsummaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/qmlbundle/main.cpp220
-rw-r--r--tools/qmlbundle/qmlbundle.pro5
-rw-r--r--tools/qmleasing/Button.qml177
-rw-r--r--tools/qmleasing/import.ui139
-rw-r--r--tools/qmleasing/main.cpp55
-rw-r--r--tools/qmleasing/mainwindow.cpp170
-rw-r--r--tools/qmleasing/mainwindow.h81
-rw-r--r--tools/qmleasing/pane.ui112
-rw-r--r--tools/qmleasing/preview.qml148
-rw-r--r--tools/qmleasing/properties.ui138
-rw-r--r--tools/qmleasing/qmleasing.pro19
-rw-r--r--tools/qmleasing/resources.qrc6
-rw-r--r--tools/qmleasing/segmentproperties.cpp128
-rw-r--r--tools/qmleasing/segmentproperties.h94
-rw-r--r--tools/qmleasing/splineeditor.cpp714
-rw-r--r--tools/qmleasing/splineeditor.h143
-rw-r--r--tools/qmlmin/main.cpp674
-rw-r--r--tools/qmlmin/qmlmin.pro5
-rw-r--r--tools/qmlplugindump/Info.plist16
-rw-r--r--tools/qmlplugindump/main.cpp776
-rw-r--r--tools/qmlplugindump/qmlplugindump.pro15
-rw-r--r--tools/qmlplugindump/qmlstreamwriter.cpp198
-rw-r--r--tools/qmlplugindump/qmlstreamwriter.h79
-rw-r--r--tools/qmlprofiler/commandlistener.cpp66
-rw-r--r--tools/qmlprofiler/commandlistener.h63
-rw-r--r--tools/qmlprofiler/constants.h59
-rw-r--r--tools/qmlprofiler/main.cpp64
-rw-r--r--tools/qmlprofiler/qmlprofiler.pro21
-rw-r--r--tools/qmlprofiler/qmlprofilerapplication.cpp434
-rw-r--r--tools/qmlprofiler/qmlprofilerapplication.h118
-rw-r--r--tools/qmlprofiler/qmlprofilerclient.cpp313
-rw-r--r--tools/qmlprofiler/qmlprofilerclient.h140
-rw-r--r--tools/qmlprofiler/qmlprofilerdata.cpp607
-rw-r--r--tools/qmlprofiler/qmlprofilerdata.h106
-rw-r--r--tools/qmlprofiler/qmlprofilereventlocation.h61
-rw-r--r--tools/qmlprofiler/qpacketprotocol.cpp546
-rw-r--r--tools/qmlprofiler/qpacketprotocol.h117
-rw-r--r--tools/qmlprofiler/qqmldebugclient.cpp409
-rw-r--r--tools/qmlprofiler/qqmldebugclient.h109
-rw-r--r--tools/qmlscene/main.cpp545
-rw-r--r--tools/qmlscene/qmlscene.pro8
-rw-r--r--tools/qmltestrunner/main.cpp51
-rw-r--r--tools/qmltestrunner/qmltestrunner.pro5
-rw-r--r--tools/tools.pro8
44 files changed, 7961 insertions, 1 deletions
diff --git a/tools/qmlbundle/main.cpp b/tools/qmlbundle/main.cpp
new file mode 100644
index 0000000000..6b0a66518f
--- /dev/null
+++ b/tools/qmlbundle/main.cpp
@@ -0,0 +1,220 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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, Digia gives you certain additional
+** rights. These rights are described in the Digia 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.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <private/qqmlbundle_p.h>
+#include <private/qqmlscript_p.h>
+#include <QtCore/QtCore>
+#include <iostream>
+
+static bool createBundle(const QString &fileName, const QStringList &fileNames)
+{
+ QQmlBundle bundle(fileName);
+ if (!bundle.open(QFile::WriteOnly))
+ return false;
+ foreach (const QString &fileName, fileNames)
+ bundle.add(fileName);
+ return true;
+}
+
+static bool removeFiles(const QString &fileName, const QStringList &fileNames)
+{
+ const QSet<QString> filesToRemove = QSet<QString>::fromList(fileNames);
+
+ QQmlBundle bundle(fileName);
+ bundle.open(QFile::ReadWrite);
+ foreach (const QQmlBundle::FileEntry *entry, bundle.files()) {
+ if (filesToRemove.contains(entry->fileName()))
+ bundle.remove(entry);
+ }
+ return true;
+}
+
+static void showHelp()
+{
+ std::cerr << "Usage: qmlbundle <command> [<args>]" << std::endl
+ << std::endl
+ << "The commands are:" << std::endl
+ << " create Create a new bundle" << std::endl
+ << " add Add files to the bundle" << std::endl
+ << " rm Remove files from the bundle" << std::endl
+ << " update Add files to the bundle or update them if they are already added" << std::endl
+ << " ls List the files in the bundle" << std::endl
+ << " cat Concatenates files and print on the standard output" << std::endl
+ << " optimize Insert optimization data for all recognised content" << std::endl
+ << std::endl
+ << "See 'qmlbundle help <command>' for more information on a specific command." << std::endl;
+}
+
+static void usage(const QString &action, const QString &error = QString())
+{
+ if (! error.isEmpty())
+ std::cerr << qPrintable(error) << std::endl << std::endl;
+
+ if (action == QLatin1String("create")) {
+ std::cerr << "usage: qmlbundle create <bundle name> [files]" << std::endl;
+ } else if (action == QLatin1String("add")) {
+ std::cerr << "usage: qmlbundle add <bundle name> [files]" << std::endl;
+ } else if (action == QLatin1String("rm")) {
+ std::cerr << "usage: qmlbundle rm <bundle name> [files]" << std::endl;
+ } else if (action == QLatin1String("update")) {
+ std::cerr << "usage: qmlbundle update <bundle name> [files]" << std::endl;
+ } else if (action == QLatin1String("ls")) {
+ std::cerr << "usage: qmlbundle ls <bundle name>" << std::endl;
+ } else if (action == QLatin1String("cat")) {
+ std::cerr << "usage: qmlbundle cat <bundle name> [files]" << std::endl;
+ } else {
+ showHelp();
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ QCoreApplication app(argc, argv);
+
+ QStringList args = app.arguments();
+ /*const QString exeName =*/ args.takeFirst();
+
+ if (args.isEmpty()) {
+ showHelp();
+ return 0;
+ }
+
+ const QString action = args.takeFirst();
+
+ if (action == QLatin1String("help")) {
+ if (args.empty())
+ showHelp();
+ else
+ usage(args.takeFirst());
+ } else if (action == QLatin1String("ls")) {
+ if (args.isEmpty()) {
+ usage(action, "You must specify a bundle");
+ return EXIT_FAILURE;
+ }
+ QQmlBundle bundle(args.takeFirst());
+ if (bundle.open(QFile::ReadOnly)) {
+ foreach (const QQmlBundle::FileEntry *fileEntry, bundle.files())
+ std::cout << qPrintable(fileEntry->fileName()) << std::endl;
+ }
+ } else if (action == QLatin1String("create")) {
+ if (args.isEmpty()) {
+ usage(action, "You must specify a bundle");
+ return EXIT_FAILURE;
+ }
+ const QString bundleFileName = args.takeFirst();
+ createBundle(bundleFileName, args);
+ } else if (action == QLatin1String("add")) {
+ if (args.isEmpty()) {
+ usage(action, "You must specify a bundle");
+ return EXIT_FAILURE;
+ }
+ const QString bundleFileName = args.takeFirst();
+ QQmlBundle bundle(bundleFileName);
+ bundle.open();
+ foreach (const QString &fileName, args) {
+ if (! bundle.add(fileName))
+ std::cerr << "cannot add file " << qPrintable(fileName) << " to " << qPrintable(bundleFileName) << std::endl;
+ }
+ } else if (action == QLatin1String("rm")) {
+ if (args.isEmpty()) {
+ usage(action, "You must specify a bundle");
+ return EXIT_FAILURE;
+ }
+ const QString bundleFileName = args.takeFirst();
+ removeFiles(bundleFileName, args);
+ } else if (action == QLatin1String("update")) {
+ if (args.isEmpty()) {
+ usage(action, "You must specify a bundle");
+ return EXIT_FAILURE;
+ }
+ const QString bundleFileName = args.takeFirst();
+ removeFiles(bundleFileName, args);
+ QQmlBundle bundle(bundleFileName);
+ bundle.open();
+ foreach (const QString &fileName, args) {
+ if (! bundle.add(fileName))
+ std::cerr << "cannot add file " << qPrintable(fileName) << " to " << qPrintable(bundleFileName) << std::endl;
+ }
+ } else if (action == QLatin1String("cat")) {
+ if (args.isEmpty()) {
+ usage(action, "You must specify a bundle");
+ return EXIT_FAILURE;
+ }
+ const QString bundleFileName = args.takeFirst();
+ QQmlBundle bundle(bundleFileName);
+ if (bundle.open(QFile::ReadOnly)) {
+ const QSet<QString> filesToShow = QSet<QString>::fromList(args);
+
+ foreach (const QQmlBundle::FileEntry *fileEntry, bundle.files()) {
+ if (filesToShow.contains(fileEntry->fileName()))
+ std::cout.write(fileEntry->contents(), fileEntry->fileSize());
+ }
+ }
+ } else if (action == QLatin1String("optimize")) {
+ if (args.isEmpty()) {
+ usage(action, "You must specify a bundle");
+ return EXIT_FAILURE;
+ }
+ const QString bundleFileName = args.takeFirst();
+ QQmlBundle bundle(bundleFileName);
+ if (bundle.open(QFile::ReadWrite)) {
+ QList<const QQmlBundle::FileEntry *> files = bundle.files();
+ for (int ii = 0; ii < files.count(); ++ii) {
+ const QQmlBundle::FileEntry *file = files.at(ii);
+
+ if (!file->fileName().endsWith(".qml"))
+ continue;
+
+ QQmlScript::Parser parser;
+ QString data = QString::fromUtf8(file->contents(), file->fileSize());
+ parser.parse(data, QByteArray());
+ QByteArray preparse = parser.preparseData();
+
+ if (!preparse.isEmpty())
+ bundle.addMetaLink(file->fileName(), QLatin1String("qml:preparse"), preparse);
+ }
+ }
+ } else {
+ showHelp();
+ }
+
+ return 0;
+}
diff --git a/tools/qmlbundle/qmlbundle.pro b/tools/qmlbundle/qmlbundle.pro
new file mode 100644
index 0000000000..440b152149
--- /dev/null
+++ b/tools/qmlbundle/qmlbundle.pro
@@ -0,0 +1,5 @@
+QT = core qml-private v8-private core-private
+
+SOURCES += main.cpp
+
+load(qt_tool)
diff --git a/tools/qmleasing/Button.qml b/tools/qmleasing/Button.qml
new file mode 100644
index 0000000000..b2fddfb18a
--- /dev/null
+++ b/tools/qmleasing/Button.qml
@@ -0,0 +1,177 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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, Digia gives you certain additional
+** rights. These rights are described in the Digia 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.
+**
+**
+** $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/qmleasing/import.ui b/tools/qmleasing/import.ui
new file mode 100644
index 0000000000..86b80e241d
--- /dev/null
+++ b/tools/qmleasing/import.ui
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ImportDialog</class>
+ <widget class="QDialog" name="ImportDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>164</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Import After Effects Curve</string>
+ </property>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="geometry">
+ <rect>
+ <x>40</x>
+ <y>130</y>
+ <width>341</width>
+ <height>32</height>
+ </rect>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ <widget class="QWidget" name="formLayoutWidget">
+ <property name="geometry">
+ <rect>
+ <x>20</x>
+ <y>10</y>
+ <width>361</width>
+ <height>101</height>
+ </rect>
+ </property>
+ <layout class="QFormLayout" name="formLayout">
+ <property name="labelAlignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Input Influence:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Output Influence:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="label_4">
+ <property name="text">
+ <string>Output Slope:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="inInfluenceEdit">
+ <property name="text">
+ <string>33</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="inSlopeEdit">
+ <property name="text">
+ <string>0</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLineEdit" name="outInfluenceEdit">
+ <property name="text">
+ <string>33</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QLineEdit" name="outSlopeEdit">
+ <property name="text">
+ <string>0</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Input Slope:</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>ImportDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>ImportDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/tools/qmleasing/main.cpp b/tools/qmleasing/main.cpp
new file mode 100644
index 0000000000..7c7f69d5f6
--- /dev/null
+++ b/tools/qmleasing/main.cpp
@@ -0,0 +1,55 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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, Digia gives you certain additional
+** rights. These rights are described in the Digia 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.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "mainwindow.h"
+
+#include <QApplication>
+
+int main(int argc, char ** argv)
+{
+ QApplication app(argc, argv);
+
+ MainWindow mainWindow;
+ mainWindow.show();
+ mainWindow.showQuickView();
+
+ return app.exec();
+}
diff --git a/tools/qmleasing/mainwindow.cpp b/tools/qmleasing/mainwindow.cpp
new file mode 100644
index 0000000000..a99c22dea6
--- /dev/null
+++ b/tools/qmleasing/mainwindow.cpp
@@ -0,0 +1,170 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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, Digia gives you certain additional
+** rights. These rights are described in the Digia 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.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "mainwindow.h"
+#include "splineeditor.h"
+#include <QtQuick/QQuickView>
+#include <QtQuick>
+#include <QtQml/QQmlContext>
+#include <QEasingCurve>
+#include <QHBoxLayout>
+#include <QVBoxLayout>
+#include <QDoubleValidator>
+#include <QDialog>
+
+MainWindow::MainWindow(QWidget *parent) :
+ QMainWindow(parent)
+{
+ setWindowTitle("QML Easing Curve Editor");
+ 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()));
+
+ QDialog* importDialog = new QDialog(this);
+ ui_import.setupUi(importDialog);
+ ui_import.inInfluenceEdit->setValidator(new QDoubleValidator(this));
+ ui_import.inSlopeEdit->setValidator(new QDoubleValidator(this));
+ ui_import.outInfluenceEdit->setValidator(new QDoubleValidator(this));
+ ui_import.outSlopeEdit->setValidator(new QDoubleValidator(this));
+ connect(ui_properties.importButton, SIGNAL(clicked()), importDialog, SLOT(show()));
+ connect(importDialog, SIGNAL(finished(int)), this, SLOT(importData(int)));
+
+ connect(this, SIGNAL(close()), this, SLOT(doClose()));
+ initQml();
+}
+
+void MainWindow::showQuickView()
+{
+ const int margin = 16;
+ quickView.setPosition(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.setFlags(Qt::FramelessWindowHint);
+ quickView.rootContext()->setContextProperty(QLatin1String("editor"), m_splineEditor);
+ quickView.setSource(QUrl("qrc:/preview.qml"));
+ quickView.show();
+}
+
+void MainWindow::closeEvent(QCloseEvent *)
+{
+ quickView.close();
+}
+
+void MainWindow::importData(int result)
+{
+ if (!result)
+ return;
+ double ii = ui_import.inInfluenceEdit->text().toDouble();
+ double is = ui_import.inSlopeEdit->text().toDouble();
+ double oi = ui_import.outInfluenceEdit->text().toDouble();
+ double os = ui_import.outSlopeEdit->text().toDouble();
+ ii = qBound<double>(0., ii, 100.) / 100.;
+ oi = qBound<double>(0., oi, 100.) / 100.;
+ QString generatedString = QString("[%1,%2,%3,%4,1,1]").arg(ii, 0, 'f', 3)
+ .arg(ii*is,0,'f',3).arg(1-oi, 0, 'f', 3).arg(1-(oi*os), 0, 'f', 3);
+ ui_properties.plainTextEdit->setPlainText(generatedString);
+}
diff --git a/tools/qmleasing/mainwindow.h b/tools/qmleasing/mainwindow.h
new file mode 100644
index 0000000000..19b1e0c63c
--- /dev/null
+++ b/tools/qmleasing/mainwindow.h
@@ -0,0 +1,81 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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, Digia gives you certain additional
+** rights. These rights are described in the Digia 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.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
+
+#include <QMainWindow>
+#include <QtQuick/QQuickView>
+#include "ui_properties.h"
+#include "ui_import.h"
+
+class SplineEditor;
+
+class MainWindow : public QMainWindow
+{
+ Q_OBJECT
+public:
+ explicit MainWindow(QWidget *parent = 0);
+
+ void showQuickView();
+
+signals:
+
+public slots:
+ void textEditTextChanged();
+ void importData(int result);
+
+protected:
+ virtual void moveEvent(QMoveEvent *event);
+ virtual void resizeEvent(QResizeEvent *event);
+ virtual void closeEvent(QCloseEvent *event);
+ void initQml();
+
+private:
+ QQuickView quickView;
+ QWidget *m_placeholder;
+ Ui_Properties ui_properties;
+ Ui_ImportDialog ui_import;
+ SplineEditor *m_splineEditor;
+
+};
+
+#endif // MAINWINDOW_H
diff --git a/tools/qmleasing/pane.ui b/tools/qmleasing/pane.ui
new file mode 100644
index 0000000000..1500589192
--- /dev/null
+++ b/tools/qmleasing/pane.ui
@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Pane</class>
+ <widget class="QWidget" name="Pane">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>416</width>
+ <height>47</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
+ <property name="font">
+ <font>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string>p1</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QWidget" name="widget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="spacing">
+ <number>4</number>
+ </property>
+ <property name="margin">
+ <number>2</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="label_x">
+ <property name="text">
+ <string>x</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDoubleSpinBox" name="p1_x">
+ <property name="decimals">
+ <number>4</number>
+ </property>
+ <property name="singleStep">
+ <double>0.010000000000000</double>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_y">
+ <property name="text">
+ <string>y</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDoubleSpinBox" name="p1_y">
+ <property name="decimals">
+ <number>4</number>
+ </property>
+ <property name="minimum">
+ <double>-10.000000000000000</double>
+ </property>
+ <property name="maximum">
+ <double>10.000000000000000</double>
+ </property>
+ <property name="singleStep">
+ <double>0.010000000000000</double>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QCheckBox" name="smooth">
+ <property name="text">
+ <string>smooth</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="3" alignment="Qt::AlignVCenter">
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>99</width>
+ <height>10</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/tools/qmleasing/preview.qml b/tools/qmleasing/preview.qml
new file mode 100644
index 0000000000..7b5249f126
--- /dev/null
+++ b/tools/qmleasing/preview.qml
@@ -0,0 +1,148 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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, Digia gives you certain additional
+** rights. These rights are described in the Digia 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.
+**
+**
+** $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/qmleasing/properties.ui b/tools/qmleasing/properties.ui
new file mode 100644
index 0000000000..149fa4f28f
--- /dev/null
+++ b/tools/qmleasing/properties.ui
@@ -0,0 +1,138 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Properties</class>
+ <widget class="QWidget" name="Properties">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>487</width>
+ <height>627</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="2">
+ <spacer name="spacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>13</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Duration</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1" colspan="2">
+ <widget class="QSpinBox" name="spinBox"/>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Code</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1" colspan="2">
+ <widget class="QPlainTextEdit" name="plainTextEdit">
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>128</height>
+ </size>
+ </property>
+ <property name="readOnly">
+ <bool>false</bool>
+ </property>
+ <property name="plainText">
+ <string notr="true"/>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Presets</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1" colspan="2">
+ <widget class="QComboBox" name="comboBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="1">
+ <spacer name="spacer_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="9" column="0" colspan="3">
+ <widget class="QGroupBox" name="groupBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>400</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="title">
+ <string>Control Points</string>
+ </property>
+ <property name="flat">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="1" colspan="2">
+ <widget class="QPushButton" name="importButton">
+ <property name="text">
+ <string>Import AfterEffects Curve</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/tools/qmleasing/qmleasing.pro b/tools/qmleasing/qmleasing.pro
new file mode 100644
index 0000000000..eadcb304c4
--- /dev/null
+++ b/tools/qmleasing/qmleasing.pro
@@ -0,0 +1,19 @@
+QT += qml quick widgets
+CONFIG -= app_bundle
+
+SOURCES += main.cpp \
+ splineeditor.cpp \
+ mainwindow.cpp \
+ segmentproperties.cpp
+
+RESOURCES = $$PWD/resources.qrc
+
+HEADERS += \
+ splineeditor.h \
+ mainwindow.h \
+ segmentproperties.h
+
+FORMS += \
+ properties.ui \
+ pane.ui \
+ import.ui
diff --git a/tools/qmleasing/resources.qrc b/tools/qmleasing/resources.qrc
new file mode 100644
index 0000000000..c184af4662
--- /dev/null
+++ b/tools/qmleasing/resources.qrc
@@ -0,0 +1,6 @@
+<RCC>
+ <qresource prefix="/">
+ <file>preview.qml</file>
+ <file>Button.qml</file>
+ </qresource>
+</RCC>
diff --git a/tools/qmleasing/segmentproperties.cpp b/tools/qmleasing/segmentproperties.cpp
new file mode 100644
index 0000000000..a8655309e5
--- /dev/null
+++ b/tools/qmleasing/segmentproperties.cpp
@@ -0,0 +1,128 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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, Digia gives you certain additional
+** rights. These rights are described in the Digia 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.
+**
+**
+** $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/qmleasing/segmentproperties.h b/tools/qmleasing/segmentproperties.h
new file mode 100644
index 0000000000..22dbd5b641
--- /dev/null
+++ b/tools/qmleasing/segmentproperties.h
@@ -0,0 +1,94 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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, Digia gives you certain additional
+** rights. These rights are described in the Digia 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.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef SEGMENTPROPERTIES_H
+#define SEGMENTPROPERTIES_H
+
+#include <QWidget>
+#include <ui_pane.h>
+
+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<QPointF> 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<QPointF> m_points;
+ int m_segment;
+ bool m_smooth;
+ bool m_last;
+
+ bool m_blockSignals;
+};
+
+#endif // SEGMENTPROPERTIES_H
diff --git a/tools/qmleasing/splineeditor.cpp b/tools/qmleasing/splineeditor.cpp
new file mode 100644
index 0000000000..fe73dd82ec
--- /dev/null
+++ b/tools/qmleasing/splineeditor.cpp
@@ -0,0 +1,714 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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, Digia gives you certain additional
+** rights. These rights are described in the Digia 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.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "splineeditor.h"
+#include "segmentproperties.h"
+
+#include <QPainter>
+#include <QMouseEvent>
+#include <QContextMenuEvent>
+#include <QDebug>
+#include <QApplication>
+
+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<QString, QEasingCurve> 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; i<m_controlPoints.size() - 1; ++i) {
+ qreal d = QLineF(point, mapToCanvas(m_controlPoints.at(i))).length();
+ if ((distance < 0 && d < 10) || d < distance) {
+ distance = d;
+ pointIndex = i;
+ }
+ }
+ return pointIndex;
+}
+
+static inline bool veryFuzzyCompare(qreal r1, qreal r2)
+{
+ if (qFuzzyCompare(r1, 2))
+ return true;
+
+ int r1i = qRound(r1 * 20);
+ int r2i = qRound(r2 * 20);
+
+ if (qFuzzyCompare(qreal(r1i) / 20, qreal(r2i) / 20))
+ return true;
+
+ return false;
+}
+
+bool SplineEditor::isSmooth(int i) const
+{
+ if (i == 0)
+ return false;
+
+ QPointF p = m_controlPoints.at(i);
+ QPointF p_before = m_controlPoints.at(i - 1);
+ QPointF p_after = m_controlPoints.at(i + 1);
+
+ QPointF v1 = p_after - p;
+ v1 = v1 / v1.manhattanLength(); //normalize
+
+ QPointF v2 = p - p_before;
+ v2 = v2 / v2.manhattanLength(); //normalize
+
+ return veryFuzzyCompare(v1.x(), v2.x()) && veryFuzzyCompare(v1.y(), v2.y());
+}
+
+void SplineEditor::smoothPoint(int index)
+{
+ if (m_smoothAction->isChecked()) {
+
+ 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.toCubicSpline();
+ 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<qreal> realList;
+ foreach (const QString &string, stringList) {
+ bool ok;
+ realList.append(string.toDouble(&ok));
+ if (!ok)
+ return;
+ }
+ QList<QPointF> 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/qmleasing/splineeditor.h b/tools/qmleasing/splineeditor.h
new file mode 100644
index 0000000000..52e81402f2
--- /dev/null
+++ b/tools/qmleasing/splineeditor.h
@@ -0,0 +1,143 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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, Digia gives you certain additional
+** rights. These rights are described in the Digia 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.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef SPLINEEDITOR_H
+#define SPLINEEDITOR_H
+
+#include <QWidget>
+#include <QMenu>
+#include <QAction>
+#include <QScrollArea>
+
+#include <QEasingCurve>
+#include <QHash>
+
+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<QString, QEasingCurve> 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<QPointF> m_controlPoints;
+ QVector<bool> m_smoothList;
+ int m_numberOfSegments;
+ int m_activeControlPoint;
+ bool m_mouseDrag;
+ QPoint m_mousePress;
+ QHash<QString, QEasingCurve> m_presets;
+
+ QMenu *m_pointContextMenu;
+ QMenu *m_curveContextMenu;
+ QAction *m_deleteAction;
+ QAction *m_smoothAction;
+ QAction *m_cornerAction;
+ QAction *m_addPoint;
+
+ QScrollArea *m_pointListWidget;
+
+ QList<SegmentProperties*> m_segmentProperties;
+ bool m_block;
+};
+
+#endif // SPLINEEDITOR_H
diff --git a/tools/qmlmin/main.cpp b/tools/qmlmin/main.cpp
new file mode 100644
index 0000000000..e3e81985a1
--- /dev/null
+++ b/tools/qmlmin/main.cpp
@@ -0,0 +1,674 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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, Digia gives you certain additional
+** rights. These rights are described in the Digia 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.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <private/qqmljsengine_p.h>
+#include <private/qqmljslexer_p.h>
+#include <private/qqmljsparser_p.h>
+#include <QtCore/QCoreApplication>
+#include <QtCore/QStringList>
+#include <QtCore/QFile>
+#include <QtCore/QFileInfo>
+#include <QtCore/QDir>
+#include <iostream>
+#include <cstdlib>
+
+QT_BEGIN_NAMESPACE
+
+//
+// QML/JS minifier
+//
+namespace QQmlJS {
+
+enum RegExpFlag {
+ Global = 0x01,
+ IgnoreCase = 0x02,
+ Multiline = 0x04
+};
+
+
+class QmlminLexer: protected Lexer, public Directives
+{
+ QQmlJS::Engine _engine;
+ QString _fileName;
+ QString _directives;
+
+public:
+ QmlminLexer(): Lexer(&_engine) {}
+ virtual ~QmlminLexer() {}
+
+ QString fileName() const { return _fileName; }
+
+ bool operator()(const QString &fileName, const QString &code)
+ {
+ int startToken = T_FEED_JS_PROGRAM;
+ const QFileInfo fileInfo(fileName);
+ if (fileInfo.suffix().toLower() == QLatin1String("qml"))
+ startToken = T_FEED_UI_PROGRAM;
+ setCode(code, /*line = */ 1, /*qmlMode = */ startToken == T_FEED_UI_PROGRAM);
+ _fileName = fileName;
+ _directives.clear();
+ return parse(startToken);
+ }
+
+ QString directives()
+ {
+ return _directives;
+ }
+
+ //
+ // Handle the .pragma/.import directives
+ //
+ virtual void pragmaLibrary()
+ {
+ _directives += QLatin1String(".pragma library\n");
+ }
+
+ virtual void importFile(const QString &jsfile, const QString &module)
+ {
+ _directives += QLatin1String(".import");
+ _directives += QLatin1Char('"');
+ _directives += quote(jsfile);
+ _directives += QLatin1Char('"');
+ _directives += QLatin1String("as ");
+ _directives += module;
+ _directives += QLatin1Char('\n');
+ }
+
+ virtual void importModule(const QString &uri, const QString &version, const QString &module)
+ {
+ _directives += QLatin1String(".import ");
+ _directives += uri;
+ _directives += QLatin1Char(' ');
+ _directives += version;
+ _directives += QLatin1String(" as ");
+ _directives += module;
+ _directives += QLatin1Char('\n');
+ }
+
+protected:
+ virtual bool parse(int startToken) = 0;
+
+ static QString quote(const QString &string)
+ {
+ QString quotedString;
+ foreach (const QChar &ch, string) {
+ if (ch == QLatin1Char('"'))
+ quotedString += QLatin1String("\\\"");
+ else {
+ if (ch == QLatin1Char('\\')) quotedString += QLatin1String("\\\\");
+ else if (ch == QLatin1Char('\"')) quotedString += QLatin1String("\\\"");
+ else if (ch == QLatin1Char('\b')) quotedString += QLatin1String("\\b");
+ else if (ch == QLatin1Char('\f')) quotedString += QLatin1String("\\f");
+ else if (ch == QLatin1Char('\n')) quotedString += QLatin1String("\\n");
+ else if (ch == QLatin1Char('\r')) quotedString += QLatin1String("\\r");
+ else if (ch == QLatin1Char('\t')) quotedString += QLatin1String("\\t");
+ else if (ch == QLatin1Char('\v')) quotedString += QLatin1String("\\v");
+ else if (ch == QLatin1Char('\0')) quotedString += QLatin1String("\\0");
+ else quotedString += ch;
+ }
+ }
+ return quotedString;
+ }
+
+ bool isIdentChar(const QChar &ch) const
+ {
+ if (ch.isLetterOrNumber())
+ return true;
+ else if (ch == QLatin1Char('_') || ch == QLatin1Char('$'))
+ return true;
+ return false;
+ }
+
+ bool isRegExpRule(int ruleno) const
+ {
+ return ruleno == J_SCRIPT_REGEXPLITERAL_RULE1 ||
+ ruleno == J_SCRIPT_REGEXPLITERAL_RULE2;
+ }
+
+ bool scanRestOfRegExp(int ruleno, QString *restOfRegExp)
+ {
+ if (! scanRegExp(ruleno == J_SCRIPT_REGEXPLITERAL_RULE1 ? Lexer::NoPrefix : Lexer::EqualPrefix))
+ return false;
+
+ *restOfRegExp = regExpPattern();
+ if (ruleno == J_SCRIPT_REGEXPLITERAL_RULE2) {
+ Q_ASSERT(! restOfRegExp->isEmpty());
+ Q_ASSERT(restOfRegExp->at(0) == QLatin1Char('='));
+ *restOfRegExp = restOfRegExp->mid(1); // strip the prefix
+ }
+ *restOfRegExp += QLatin1Char('/');
+ const RegExpFlag flags = (RegExpFlag) regExpFlags();
+ if (flags & Global)
+ *restOfRegExp += QLatin1Char('g');
+ if (flags & IgnoreCase)
+ *restOfRegExp += QLatin1Char('i');
+ if (flags & Multiline)
+ *restOfRegExp += QLatin1Char('m');
+
+ if (regExpFlags() == 0) {
+ // Add an extra space after the regexp literal delimiter (aka '/').
+ // This will avoid possible problems when pasting tokens like `instanceof'
+ // after the regexp literal.
+ *restOfRegExp += QLatin1Char(' ');
+ }
+ return true;
+ }
+};
+
+
+class Minify: public QmlminLexer
+{
+ QVector<int> _stateStack;
+ QList<int> _tokens;
+ QList<QString> _tokenStrings;
+ QString _minifiedCode;
+ int _maxWidth;
+ int _width;
+
+public:
+ Minify(int maxWidth);
+
+ QString minifiedCode() const;
+
+protected:
+ void append(const QString &s);
+ bool parse(int startToken);
+ void escape(const QChar &ch, QString *out);
+};
+
+Minify::Minify(int maxWidth)
+ : _stateStack(128), _maxWidth(maxWidth), _width(0)
+{
+}
+
+QString Minify::minifiedCode() const
+{
+ return _minifiedCode;
+}
+
+void Minify::append(const QString &s)
+{
+ if (!s.isEmpty()) {
+ if (_maxWidth) {
+ // Prefer not to exceed the maximum chars per line (but don't break up segments)
+ int segmentLength = s.count();
+ if (_width && ((_width + segmentLength) > _maxWidth)) {
+ _minifiedCode.append(QLatin1Char('\n'));
+ _width = 0;
+ }
+
+ _width += segmentLength;
+ }
+
+ _minifiedCode.append(s);
+ }
+}
+
+void Minify::escape(const QChar &ch, QString *out)
+{
+ out->append(QLatin1String("\\u"));
+ const QString hx = QString::number(ch.unicode(), 16);
+ switch (hx.length()) {
+ case 1: out->append(QLatin1String("000")); break;
+ case 2: out->append(QLatin1String("00")); break;
+ case 3: out->append(QLatin1String("0")); break;
+ case 4: break;
+ default: Q_ASSERT(!"unreachable");
+ }
+ out->append(hx);
+}
+
+bool Minify::parse(int startToken)
+{
+ int yyaction = 0;
+ int yytoken = -1;
+ int yytos = -1;
+ QString yytokentext;
+ QString assembled;
+
+ _minifiedCode.clear();
+ _tokens.append(startToken);
+ _tokenStrings.append(QString());
+
+ if (startToken == T_FEED_JS_PROGRAM) {
+ // parse optional pragma directive
+ if (scanDirectives(this)) {
+ // append the scanned directives to the minifier code.
+ append(directives());
+
+ _tokens.append(tokenKind());
+ _tokenStrings.append(tokenText());
+ } else {
+ std::cerr << qPrintable(fileName()) << ":" << tokenStartLine() << ":" << tokenStartColumn() << ": syntax error" << std::endl;
+ return false;
+ }
+ }
+
+ do {
+ if (++yytos == _stateStack.size())
+ _stateStack.resize(_stateStack.size() * 2);
+
+ _stateStack[yytos] = yyaction;
+
+ again:
+ if (yytoken == -1 && action_index[yyaction] != -TERMINAL_COUNT) {
+ if (_tokens.isEmpty()) {
+ _tokens.append(lex());
+ _tokenStrings.append(tokenText());
+ }
+
+ yytoken = _tokens.takeFirst();
+ yytokentext = _tokenStrings.takeFirst();
+ }
+
+ yyaction = t_action(yyaction, yytoken);
+ if (yyaction > 0) {
+ if (yyaction == ACCEPT_STATE) {
+ --yytos;
+ if (!assembled.isEmpty())
+ append(assembled);
+ return true;
+ }
+
+ const QChar lastChar = assembled.isEmpty() ? (_minifiedCode.isEmpty() ? QChar()
+ : _minifiedCode.at(_minifiedCode.length() - 1))
+ : assembled.at(assembled.length() - 1);
+
+ if (yytoken == T_SEMICOLON) {
+ assembled += QLatin1Char(';');
+
+ append(assembled);
+ assembled.clear();
+
+ } else if (yytoken == T_PLUS || yytoken == T_MINUS || yytoken == T_PLUS_PLUS || yytoken == T_MINUS_MINUS) {
+ if (lastChar == QLatin1Char(spell[yytoken][0])) {
+ // don't merge unary signs, additive expressions and postfix/prefix increments.
+ assembled += QLatin1Char(' ');
+ }
+
+ assembled += QLatin1String(spell[yytoken]);
+
+ } else if (yytoken == T_NUMERIC_LITERAL) {
+ if (isIdentChar(lastChar))
+ assembled += QLatin1Char(' ');
+
+ if (yytokentext.startsWith('.'))
+ assembled += QLatin1Char('0');
+
+ assembled += yytokentext;
+
+ if (assembled.endsWith(QLatin1Char('.')))
+ assembled += QLatin1Char('0');
+
+ } else if (yytoken == T_IDENTIFIER) {
+ QString identifier = yytokentext;
+
+ if (classify(identifier.constData(), identifier.size(), qmlMode()) != T_IDENTIFIER) {
+ // the unescaped identifier is a keyword. In this case just replace
+ // the last character of the identifier with it escape sequence.
+ const QChar ch = identifier.at(identifier.length() - 1);
+ identifier.chop(1);
+ escape(ch, &identifier);
+ }
+
+ if (isIdentChar(lastChar))
+ assembled += QLatin1Char(' ');
+
+ assembled += identifier;
+
+ } else if (yytoken == T_STRING_LITERAL || yytoken == T_MULTILINE_STRING_LITERAL) {
+ assembled += QLatin1Char('"');
+ assembled += quote(yytokentext);
+ assembled += QLatin1Char('"');
+ } else {
+ if (isIdentChar(lastChar)) {
+ if (! yytokentext.isEmpty()) {
+ const QChar ch = yytokentext.at(0);
+ if (isIdentChar(ch))
+ assembled += QLatin1Char(' ');
+ }
+ }
+ assembled += yytokentext;
+ }
+ yytoken = -1;
+ } else if (yyaction < 0) {
+ const int ruleno = -yyaction - 1;
+ yytos -= rhs[ruleno];
+
+ if (isRegExpRule(ruleno)) {
+ QString restOfRegExp;
+
+ if (! scanRestOfRegExp(ruleno, &restOfRegExp))
+ break; // break the loop, it wil report a syntax error
+
+ assembled += restOfRegExp;
+ }
+ yyaction = nt_action(_stateStack[yytos], lhs[ruleno] - TERMINAL_COUNT);
+ }
+ } while (yyaction);
+
+ const int yyerrorstate = _stateStack[yytos];
+
+ // automatic insertion of `;'
+ if (yytoken != -1 && ((t_action(yyerrorstate, T_AUTOMATIC_SEMICOLON) && canInsertAutomaticSemicolon(yytoken))
+ || t_action(yyerrorstate, T_COMPATIBILITY_SEMICOLON))) {
+ _tokens.prepend(yytoken);
+ _tokenStrings.prepend(yytokentext);
+ yyaction = yyerrorstate;
+ yytoken = T_SEMICOLON;
+ goto again;
+ }
+
+ std::cerr << qPrintable(fileName()) << ":" << tokenStartLine() << ":" << tokenStartColumn() << ": syntax error" << std::endl;
+ return false;
+}
+
+
+class Tokenize: public QmlminLexer
+{
+ QVector<int> _stateStack;
+ QList<int> _tokens;
+ QList<QString> _tokenStrings;
+ QStringList _minifiedCode;
+
+public:
+ Tokenize();
+
+ QStringList tokenStream() const;
+
+protected:
+ virtual bool parse(int startToken);
+};
+
+Tokenize::Tokenize()
+ : _stateStack(128)
+{
+}
+
+QStringList Tokenize::tokenStream() const
+{
+ return _minifiedCode;
+}
+
+bool Tokenize::parse(int startToken)
+{
+ int yyaction = 0;
+ int yytoken = -1;
+ int yytos = -1;
+ QString yytokentext;
+
+ _minifiedCode.clear();
+ _tokens.append(startToken);
+ _tokenStrings.append(QString());
+
+ if (startToken == T_FEED_JS_PROGRAM) {
+ // parse optional pragma directive
+ if (scanDirectives(this)) {
+ // append the scanned directives as one token to
+ // the token stream.
+ _minifiedCode.append(directives());
+
+ _tokens.append(tokenKind());
+ _tokenStrings.append(tokenText());
+ } else {
+ std::cerr << qPrintable(fileName()) << ":" << tokenStartLine() << ":" << tokenStartColumn() << ": syntax error" << std::endl;
+ return false;
+ }
+ }
+
+ do {
+ if (++yytos == _stateStack.size())
+ _stateStack.resize(_stateStack.size() * 2);
+
+ _stateStack[yytos] = yyaction;
+
+ again:
+ if (yytoken == -1 && action_index[yyaction] != -TERMINAL_COUNT) {
+ if (_tokens.isEmpty()) {
+ _tokens.append(lex());
+ _tokenStrings.append(tokenText());
+ }
+
+ yytoken = _tokens.takeFirst();
+ yytokentext = _tokenStrings.takeFirst();
+ }
+
+ yyaction = t_action(yyaction, yytoken);
+ if (yyaction > 0) {
+ if (yyaction == ACCEPT_STATE) {
+ --yytos;
+ return true;
+ }
+
+ if (yytoken == T_SEMICOLON)
+ _minifiedCode += QLatin1String(";");
+ else
+ _minifiedCode += yytokentext;
+
+ yytoken = -1;
+ } else if (yyaction < 0) {
+ const int ruleno = -yyaction - 1;
+ yytos -= rhs[ruleno];
+
+ if (isRegExpRule(ruleno)) {
+ QString restOfRegExp;
+
+ if (! scanRestOfRegExp(ruleno, &restOfRegExp))
+ break; // break the loop, it wil report a syntax error
+
+ _minifiedCode.last().append(restOfRegExp);
+ }
+
+ yyaction = nt_action(_stateStack[yytos], lhs[ruleno] - TERMINAL_COUNT);
+ }
+ } while (yyaction);
+
+ const int yyerrorstate = _stateStack[yytos];
+
+ // automatic insertion of `;'
+ if (yytoken != -1 && ((t_action(yyerrorstate, T_AUTOMATIC_SEMICOLON) && canInsertAutomaticSemicolon(yytoken))
+ || t_action(yyerrorstate, T_COMPATIBILITY_SEMICOLON))) {
+ _tokens.prepend(yytoken);
+ _tokenStrings.prepend(yytokentext);
+ yyaction = yyerrorstate;
+ yytoken = T_SEMICOLON;
+ goto again;
+ }
+
+ std::cerr << qPrintable(fileName()) << ":" << tokenStartLine() << ":" << tokenStartColumn() << ": syntax error" << std::endl;
+ return false;
+}
+
+} // end of QQmlJS namespace
+
+static void usage(bool showHelp = false)
+{
+ std::cerr << "Usage: qmlmin [options] file" << std::endl;
+
+ if (showHelp) {
+ std::cerr << " Removes comments and layout characters" << std::endl
+ << " The options are:" << std::endl
+ << " -o<file> write output to file rather than stdout" << std::endl
+ << " -v --verify-only just run the verifier, no output" << std::endl
+ << " -w<width> restrict line characters to width" << std::endl
+ << " -h display this output" << std::endl;
+ }
+}
+
+int runQmlmin(int argc, char *argv[])
+{
+ QCoreApplication app(argc, argv);
+
+ const QStringList args = app.arguments();
+
+ QString fileName;
+ QString outputFile;
+ bool verifyOnly = false;
+
+ // By default ensure the output character width is less than 16-bits (pass 0 to disable)
+ int width = USHRT_MAX;
+
+ int index = 1;
+ while (index < args.size()) {
+ const QString arg = args.at(index++);
+ const QString next = index < args.size() ? args.at(index) : QString();
+
+ if (arg == QLatin1String("-h") || arg == QLatin1String("--help")) {
+ usage(/*showHelp*/ true);
+ return 0;
+ } else if (arg == QLatin1String("-v") || arg == QLatin1String("--verify-only")) {
+ verifyOnly = true;
+ } else if (arg == QLatin1String("-o")) {
+ if (next.isEmpty()) {
+ std::cerr << "qmlmin: argument to '-o' is missing" << std::endl;
+ return EXIT_FAILURE;
+ } else {
+ outputFile = next;
+ ++index; // consume the next argument
+ }
+ } else if (arg.startsWith(QLatin1String("-o"))) {
+ outputFile = arg.mid(2);
+
+ if (outputFile.isEmpty()) {
+ std::cerr << "qmlmin: argument to '-o' is missing" << std::endl;
+ return EXIT_FAILURE;
+ }
+ } else if (arg == QLatin1String("-w")) {
+ if (next.isEmpty()) {
+ std::cerr << "qmlmin: argument to '-w' is missing" << std::endl;
+ return EXIT_FAILURE;
+ } else {
+ bool ok;
+ width = next.toInt(&ok);
+
+ if (!ok) {
+ std::cerr << "qmlmin: argument to '-w' is invalid" << std::endl;
+ return EXIT_FAILURE;
+ }
+
+ ++index; // consume the next argument
+ }
+ } else if (arg.startsWith(QLatin1String("-w"))) {
+ bool ok;
+ width = arg.mid(2).toInt(&ok);
+
+ if (!ok) {
+ std::cerr << "qmlmin: argument to '-w' is invalid" << std::endl;
+ return EXIT_FAILURE;
+ }
+ } else {
+ const bool isInvalidOpt = arg.startsWith(QLatin1Char('-'));
+ if (! isInvalidOpt && fileName.isEmpty())
+ fileName = arg;
+ else {
+ usage(/*show help*/ isInvalidOpt);
+ if (isInvalidOpt)
+ std::cerr << "qmlmin: invalid option '" << qPrintable(arg) << "'" << std::endl;
+ else
+ std::cerr << "qmlmin: too many input files specified" << std::endl;
+ return EXIT_FAILURE;
+ }
+ }
+ }
+
+ if (fileName.isEmpty()) {
+ usage();
+ return 0;
+ }
+
+ QFile file(fileName);
+ if (! file.open(QFile::ReadOnly)) {
+ std::cerr << "qmlmin: '" << qPrintable(fileName) << "' no such file or directory" << std::endl;
+ return EXIT_FAILURE;
+ }
+
+ const QString code = QString::fromUtf8(file.readAll()); // QML files are UTF-8 encoded.
+ file.close();
+
+ QQmlJS::Minify minify(width);
+ if (! minify(fileName, code)) {
+ std::cerr << "qmlmin: cannot minify '" << qPrintable(fileName) << "' (not a valid QML/JS file)" << std::endl;
+ return EXIT_FAILURE;
+ }
+
+ //
+ // verify the output
+ //
+ QQmlJS::Minify secondMinify(width);
+ if (! secondMinify(fileName, minify.minifiedCode()) || secondMinify.minifiedCode() != minify.minifiedCode()) {
+ std::cerr << "qmlmin: cannot minify '" << qPrintable(fileName) << "'" << std::endl;
+ return EXIT_FAILURE;
+ }
+
+ QQmlJS::Tokenize originalTokens, minimizedTokens;
+ originalTokens(fileName, code);
+ minimizedTokens(fileName, minify.minifiedCode());
+
+ if (originalTokens.tokenStream().size() != minimizedTokens.tokenStream().size()) {
+ std::cerr << "qmlmin: cannot minify '" << qPrintable(fileName) << "'" << std::endl;
+ return EXIT_FAILURE;
+ }
+
+ if (! verifyOnly) {
+ if (outputFile.isEmpty()) {
+ const QByteArray chars = minify.minifiedCode().toUtf8();
+ std::cout << chars.constData();
+ } else {
+ QFile file(outputFile);
+ if (! file.open(QFile::WriteOnly)) {
+ std::cerr << "qmlmin: cannot minify '" << qPrintable(fileName) << "' (permission denied)" << std::endl;
+ return EXIT_FAILURE;
+ }
+
+ file.write(minify.minifiedCode().toUtf8());
+ file.close();
+ }
+ }
+
+ return 0;
+}
+
+QT_END_NAMESPACE
+
+int main(int argc, char **argv)
+{
+ return QT_PREPEND_NAMESPACE(runQmlmin(argc, argv));
+}
diff --git a/tools/qmlmin/qmlmin.pro b/tools/qmlmin/qmlmin.pro
new file mode 100644
index 0000000000..2cbf196863
--- /dev/null
+++ b/tools/qmlmin/qmlmin.pro
@@ -0,0 +1,5 @@
+option(host_build)
+QT = core qmldevtools-private
+SOURCES += main.cpp
+
+load(qt_tool)
diff --git a/tools/qmlplugindump/Info.plist b/tools/qmlplugindump/Info.plist
new file mode 100644
index 0000000000..f35846d048
--- /dev/null
+++ b/tools/qmlplugindump/Info.plist
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleSignature</key>
+ <string>@TYPEINFO@</string>
+ <key>CFBundleExecutable</key>
+ <string>@EXECUTABLE@</string>
+ <key>CFBundleIdentifier</key>
+ <string>com.nokia.qt.qmlplugindump</string>
+ <key>LSUIElement</key>
+ <string>1</string>
+</dict>
+</plist>
diff --git a/tools/qmlplugindump/main.cpp b/tools/qmlplugindump/main.cpp
new file mode 100644
index 0000000000..e05c77cbfa
--- /dev/null
+++ b/tools/qmlplugindump/main.cpp
@@ -0,0 +1,776 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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, Digia gives you certain additional
+** rights. These rights are described in the Digia 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.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtQml/qqmlengine.h>
+#include <QtQml/private/qqmlmetatype_p.h>
+#include <QtQml/private/qqmlopenmetaobject_p.h>
+#include <QtQuick/private/qquickevents_p_p.h>
+#include <QtQuick/private/qquickpincharea_p.h>
+
+#include <QtGui/QGuiApplication>
+#include <QtCore/QDir>
+#include <QtCore/QFileInfo>
+#include <QtCore/QSet>
+#include <QtCore/QStringList>
+#include <QtCore/QTimer>
+#include <QtCore/QMetaObject>
+#include <QtCore/QMetaProperty>
+#include <QtCore/QDebug>
+#include <QtCore/private/qobject_p.h>
+#include <QtCore/private/qmetaobject_p.h>
+
+#include <iostream>
+
+#include "qmlstreamwriter.h"
+
+#ifdef QT_SIMULATOR
+#include <QtGui/private/qsimulatorconnection_p.h>
+#endif
+
+#ifdef Q_OS_UNIX
+#include <signal.h>
+#endif
+
+QString pluginImportPath;
+bool verbose = false;
+
+QString currentProperty;
+QString inObjectInstantiation;
+
+void collectReachableMetaObjects(const QMetaObject *meta, QSet<const QMetaObject *> *metas, bool extended = false)
+{
+ if (! meta || metas->contains(meta))
+ return;
+
+ // dynamic meta objects can break things badly
+ // but extended types are usually fine
+ const QMetaObjectPrivate *mop = reinterpret_cast<const QMetaObjectPrivate *>(meta->d.data);
+ if (extended || !(mop->flags & DynamicMetaObject))
+ metas->insert(meta);
+
+ collectReachableMetaObjects(meta->superClass(), metas);
+}
+
+void collectReachableMetaObjects(QObject *object, QSet<const QMetaObject *> *metas)
+{
+ if (! object)
+ return;
+
+ const QMetaObject *meta = object->metaObject();
+ if (verbose)
+ qDebug() << "Processing object" << meta->className();
+ collectReachableMetaObjects(meta, metas);
+
+ for (int index = 0; index < meta->propertyCount(); ++index) {
+ QMetaProperty prop = meta->property(index);
+ if (QQmlMetaType::isQObject(prop.userType())) {
+ if (verbose)
+ qDebug() << " Processing property" << prop.name();
+ currentProperty = QString("%1::%2").arg(meta->className(), prop.name());
+
+ // if the property was not initialized during construction,
+ // accessing a member of oo is going to cause a segmentation fault
+ QObject *oo = QQmlMetaType::toQObject(prop.read(object));
+ if (oo && !metas->contains(oo->metaObject()))
+ collectReachableMetaObjects(oo, metas);
+ currentProperty.clear();
+ }
+ }
+}
+
+void collectReachableMetaObjects(const QQmlType *ty, QSet<const QMetaObject *> *metas)
+{
+ collectReachableMetaObjects(ty->metaObject(), metas, ty->isExtendedType());
+ if (ty->attachedPropertiesType())
+ collectReachableMetaObjects(ty->attachedPropertiesType(), metas);
+}
+
+/* We want to add the MetaObject for 'Qt' to the list, this is a
+ simple way to access it.
+*/
+class FriendlyQObject: public QObject
+{
+public:
+ static const QMetaObject *qtMeta() { return &staticQtMetaObject; }
+};
+
+/* When we dump a QMetaObject, we want to list all the types it is exported as.
+ To do this, we need to find the QQmlTypes associated with this
+ QMetaObject.
+*/
+static QHash<QByteArray, QSet<const QQmlType *> > qmlTypesByCppName;
+
+static QHash<QByteArray, QByteArray> cppToId;
+
+/* Takes a C++ type name, such as Qt::LayoutDirection or QString and
+ maps it to how it should appear in the description file.
+
+ These names need to be unique globally, so we don't change the C++ symbol's
+ name much. It is mostly used to for explicit translations such as
+ QString->string and translations for extended QML objects.
+*/
+QByteArray convertToId(const QByteArray &cppName)
+{
+ return cppToId.value(cppName, cppName);
+}
+
+QByteArray convertToId(const QMetaObject *mo)
+{
+ QByteArray className(mo->className());
+ if (!className.isEmpty())
+ return convertToId(className);
+
+ // likely a metaobject generated for an extended qml object
+ if (mo->superClass()) {
+ className = convertToId(mo->superClass());
+ className.append("_extended");
+ return className;
+ }
+
+ static QHash<const QMetaObject *, QByteArray> generatedNames;
+ className = generatedNames.value(mo);
+ if (!className.isEmpty())
+ return className;
+
+ qWarning() << "Found a QMetaObject without a className, generating a random name";
+ className = QByteArray("error-unknown-name-");
+ className.append(QByteArray::number(generatedNames.size()));
+ generatedNames.insert(mo, className);
+ return className;
+}
+
+QSet<const QMetaObject *> collectReachableMetaObjects(QQmlEngine *engine, const QList<QQmlType *> &skip = QList<QQmlType *>())
+{
+ QSet<const QMetaObject *> metas;
+ metas.insert(FriendlyQObject::qtMeta());
+
+ QHash<QByteArray, QSet<QByteArray> > extensions;
+ foreach (const QQmlType *ty, QQmlMetaType::qmlTypes()) {
+ if (!ty->isComposite()) {
+ qmlTypesByCppName[ty->metaObject()->className()].insert(ty);
+ if (ty->isExtendedType())
+ extensions[ty->typeName()].insert(ty->metaObject()->className());
+ collectReachableMetaObjects(ty, &metas);
+ }
+ // TODO actually handle composite types
+ }
+
+ // Adjust exports of the base object if there are extensions.
+ // For each export of a base object there can be a single extension object overriding it.
+ // Example: QDeclarativeGraphicsWidget overrides the QtQuick/QGraphicsWidget export
+ // of QGraphicsWidget.
+ foreach (const QByteArray &baseCpp, extensions.keys()) {
+ QSet<const QQmlType *> baseExports = qmlTypesByCppName.value(baseCpp);
+
+ const QSet<QByteArray> extensionCppNames = extensions.value(baseCpp);
+ foreach (const QByteArray &extensionCppName, extensionCppNames) {
+ const QSet<const QQmlType *> extensionExports = qmlTypesByCppName.value(extensionCppName);
+
+ // remove extension exports from base imports
+ // unfortunately the QQmlType pointers don't match, so can't use QSet::subtract
+ QSet<const QQmlType *> newBaseExports;
+ foreach (const QQmlType *baseExport, baseExports) {
+ bool match = false;
+ foreach (const QQmlType *extensionExport, extensionExports) {
+ if (baseExport->qmlTypeName() == extensionExport->qmlTypeName()
+ && baseExport->majorVersion() == extensionExport->majorVersion()
+ && baseExport->minorVersion() == extensionExport->minorVersion()) {
+ match = true;
+ break;
+ }
+ }
+ if (!match)
+ newBaseExports.insert(baseExport);
+ }
+ baseExports = newBaseExports;
+ }
+ qmlTypesByCppName[baseCpp] = baseExports;
+ }
+
+ // find even more QMetaObjects by instantiating QML types and running
+ // over the instances
+ foreach (QQmlType *ty, QQmlMetaType::qmlTypes()) {
+ if (skip.contains(ty))
+ continue;
+ if (ty->isExtendedType())
+ continue;
+ if (!ty->isCreatable())
+ continue;
+ if (ty->typeName() == "QQmlComponent")
+ continue;
+
+ QString tyName = ty->qmlTypeName();
+ tyName = tyName.mid(tyName.lastIndexOf(QLatin1Char('/')) + 1);
+ if (tyName.isEmpty())
+ continue;
+
+ inObjectInstantiation = tyName;
+ QObject *object = 0;
+
+ if (ty->isSingleton()) {
+ QQmlType::SingletonInstanceInfo *siinfo = ty->singletonInstanceInfo();
+ if (siinfo->qobjectCallback) {
+ siinfo->init(engine);
+ collectReachableMetaObjects(object, &metas);
+ object = siinfo->qobjectApi(engine);
+ } else {
+ inObjectInstantiation.clear();
+ continue; // we don't handle QJSValue singleton types.
+ }
+ } else {
+ object = ty->create();
+ }
+
+ inObjectInstantiation.clear();
+
+ if (object)
+ collectReachableMetaObjects(object, &metas);
+ else
+ qWarning() << "Could not create" << tyName;
+ }
+
+ return metas;
+}
+
+
+class Dumper
+{
+ QmlStreamWriter *qml;
+ QString relocatableModuleUri;
+
+public:
+ Dumper(QmlStreamWriter *qml) : qml(qml) {}
+
+ void setRelocatableModuleUri(const QString &uri)
+ {
+ relocatableModuleUri = uri;
+ }
+
+ void dump(const QMetaObject *meta)
+ {
+ qml->writeStartObject("Component");
+
+ QByteArray id = convertToId(meta);
+ qml->writeScriptBinding(QLatin1String("name"), enquote(id));
+
+ for (int index = meta->classInfoCount() - 1 ; index >= 0 ; --index) {
+ QMetaClassInfo classInfo = meta->classInfo(index);
+ if (QLatin1String(classInfo.name()) == QLatin1String("DefaultProperty")) {
+ qml->writeScriptBinding(QLatin1String("defaultProperty"), enquote(QLatin1String(classInfo.value())));
+ break;
+ }
+ }
+
+ if (meta->superClass())
+ qml->writeScriptBinding(QLatin1String("prototype"), enquote(convertToId(meta->superClass())));
+
+ QSet<const QQmlType *> qmlTypes = qmlTypesByCppName.value(meta->className());
+ if (!qmlTypes.isEmpty()) {
+ QHash<QString, const QQmlType *> exports;
+
+ foreach (const QQmlType *qmlTy, qmlTypes) {
+ QString qmlTyName = qmlTy->qmlTypeName();
+ if (qmlTyName.startsWith(relocatableModuleUri + QLatin1Char('/'))) {
+ qmlTyName.remove(0, relocatableModuleUri.size() + 1);
+ }
+ if (qmlTyName.startsWith("./")) {
+ qmlTyName.remove(0, 2);
+ }
+ if (qmlTyName.startsWith("/")) {
+ qmlTyName.remove(0, 1);
+ }
+ const QString exportString = enquote(
+ QString("%1 %2.%3").arg(
+ qmlTyName,
+ QString::number(qmlTy->majorVersion()),
+ QString::number(qmlTy->minorVersion())));
+ exports.insert(exportString, qmlTy);
+ }
+
+ // ensure exports are sorted and don't change order when the plugin is dumped again
+ QStringList exportStrings = exports.keys();
+ qSort(exportStrings);
+ qml->writeArrayBinding(QLatin1String("exports"), exportStrings);
+
+ // write meta object revisions unless they're all zero
+ QStringList metaObjectRevisions;
+ bool shouldWriteMetaObjectRevisions = false;
+ foreach (const QString &exportString, exportStrings) {
+ int metaObjectRevision = exports[exportString]->metaObjectRevision();
+ if (metaObjectRevision != 0)
+ shouldWriteMetaObjectRevisions = true;
+ metaObjectRevisions += QString::number(metaObjectRevision);
+ }
+ if (shouldWriteMetaObjectRevisions)
+ qml->writeArrayBinding(QLatin1String("exportMetaObjectRevisions"), metaObjectRevisions);
+
+ if (const QMetaObject *attachedType = (*qmlTypes.begin())->attachedPropertiesType()) {
+ // Can happen when a type is registered that returns itself as attachedPropertiesType()
+ // because there is no creatable type to attach to.
+ if (attachedType != meta) {
+ qml->writeScriptBinding(QLatin1String("attachedType"), enquote(
+ convertToId(attachedType)));
+ }
+ }
+ }
+
+ for (int index = meta->enumeratorOffset(); index < meta->enumeratorCount(); ++index)
+ dump(meta->enumerator(index));
+
+ QSet<QString> implicitSignals;
+ for (int index = meta->propertyOffset(); index < meta->propertyCount(); ++index) {
+ const QMetaProperty &property = meta->property(index);
+ dump(property);
+ implicitSignals.insert(QString("%1Changed").arg(QString::fromUtf8(property.name())));
+ }
+
+ if (meta == &QObject::staticMetaObject) {
+ // for QObject, hide deleteLater() and onDestroyed
+ for (int index = meta->methodOffset(); index < meta->methodCount(); ++index) {
+ QMetaMethod method = meta->method(index);
+ QByteArray signature = method.methodSignature();
+ if (signature == QByteArrayLiteral("destroyed(QObject*)")
+ || signature == QByteArrayLiteral("destroyed()")
+ || signature == QByteArrayLiteral("deleteLater()"))
+ continue;
+ dump(method, implicitSignals);
+ }
+
+ // and add toString(), destroy() and destroy(int)
+ qml->writeStartObject(QLatin1String("Method"));
+ qml->writeScriptBinding(QLatin1String("name"), enquote(QLatin1String("toString")));
+ qml->writeEndObject();
+ qml->writeStartObject(QLatin1String("Method"));
+ qml->writeScriptBinding(QLatin1String("name"), enquote(QLatin1String("destroy")));
+ qml->writeEndObject();
+ qml->writeStartObject(QLatin1String("Method"));
+ qml->writeScriptBinding(QLatin1String("name"), enquote(QLatin1String("destroy")));
+ qml->writeStartObject(QLatin1String("Parameter"));
+ qml->writeScriptBinding(QLatin1String("name"), enquote(QLatin1String("delay")));
+ qml->writeScriptBinding(QLatin1String("type"), enquote(QLatin1String("int")));
+ qml->writeEndObject();
+ qml->writeEndObject();
+ } else {
+ for (int index = meta->methodOffset(); index < meta->methodCount(); ++index)
+ dump(meta->method(index), implicitSignals);
+ }
+
+ qml->writeEndObject();
+ }
+
+ void writeEasingCurve()
+ {
+ qml->writeStartObject(QLatin1String("Component"));
+ qml->writeScriptBinding(QLatin1String("name"), enquote(QLatin1String("QEasingCurve")));
+ qml->writeScriptBinding(QLatin1String("prototype"), enquote(QLatin1String("QQmlEasingValueType")));
+ qml->writeEndObject();
+ }
+
+private:
+ static QString enquote(const QString &string)
+ {
+ return QString("\"%1\"").arg(string);
+ }
+
+ /* Removes pointer and list annotations from a type name, returning
+ what was removed in isList and isPointer
+ */
+ static void removePointerAndList(QByteArray *typeName, bool *isList, bool *isPointer)
+ {
+ static QByteArray declListPrefix = "QQmlListProperty<";
+
+ if (typeName->endsWith('*')) {
+ *isPointer = true;
+ typeName->truncate(typeName->length() - 1);
+ removePointerAndList(typeName, isList, isPointer);
+ } else if (typeName->startsWith(declListPrefix)) {
+ *isList = true;
+ typeName->truncate(typeName->length() - 1); // get rid of the suffix '>'
+ *typeName = typeName->mid(declListPrefix.size());
+ removePointerAndList(typeName, isList, isPointer);
+ }
+
+ *typeName = convertToId(*typeName);
+ }
+
+ void writeTypeProperties(QByteArray typeName, bool isWritable)
+ {
+ bool isList = false, isPointer = false;
+ removePointerAndList(&typeName, &isList, &isPointer);
+
+ qml->writeScriptBinding(QLatin1String("type"), enquote(typeName));
+ if (isList)
+ qml->writeScriptBinding(QLatin1String("isList"), QLatin1String("true"));
+ if (!isWritable)
+ qml->writeScriptBinding(QLatin1String("isReadonly"), QLatin1String("true"));
+ if (isPointer)
+ qml->writeScriptBinding(QLatin1String("isPointer"), QLatin1String("true"));
+ }
+
+ void dump(const QMetaProperty &prop)
+ {
+ qml->writeStartObject("Property");
+
+ qml->writeScriptBinding(QLatin1String("name"), enquote(QString::fromUtf8(prop.name())));
+#if (QT_VERSION >= QT_VERSION_CHECK(4, 7, 4))
+ if (int revision = prop.revision())
+ qml->writeScriptBinding(QLatin1String("revision"), QString::number(revision));
+#endif
+ writeTypeProperties(prop.typeName(), prop.isWritable());
+
+ qml->writeEndObject();
+ }
+
+ void dump(const QMetaMethod &meth, const QSet<QString> &implicitSignals)
+ {
+ if (meth.methodType() == QMetaMethod::Signal) {
+ if (meth.access() != QMetaMethod::Protected)
+ return; // nothing to do.
+ } else if (meth.access() != QMetaMethod::Public) {
+ return; // nothing to do.
+ }
+
+ QByteArray name = meth.name();
+ const QString typeName = convertToId(meth.typeName());
+
+ if (implicitSignals.contains(name)
+ && !meth.revision()
+ && meth.methodType() == QMetaMethod::Signal
+ && meth.parameterNames().isEmpty()
+ && typeName == QLatin1String("void")) {
+ // don't mention implicit signals
+ return;
+ }
+
+ if (meth.methodType() == QMetaMethod::Signal)
+ qml->writeStartObject(QLatin1String("Signal"));
+ else
+ qml->writeStartObject(QLatin1String("Method"));
+
+ qml->writeScriptBinding(QLatin1String("name"), enquote(name));
+
+#if (QT_VERSION >= QT_VERSION_CHECK(4, 7, 4))
+ if (int revision = meth.revision())
+ qml->writeScriptBinding(QLatin1String("revision"), QString::number(revision));
+#endif
+
+ if (typeName != QLatin1String("void"))
+ qml->writeScriptBinding(QLatin1String("type"), enquote(typeName));
+
+ for (int i = 0; i < meth.parameterTypes().size(); ++i) {
+ QByteArray argName = meth.parameterNames().at(i);
+
+ qml->writeStartObject(QLatin1String("Parameter"));
+ if (! argName.isEmpty())
+ qml->writeScriptBinding(QLatin1String("name"), enquote(argName));
+ writeTypeProperties(meth.parameterTypes().at(i), true);
+ qml->writeEndObject();
+ }
+
+ qml->writeEndObject();
+ }
+
+ void dump(const QMetaEnum &e)
+ {
+ qml->writeStartObject(QLatin1String("Enum"));
+ qml->writeScriptBinding(QLatin1String("name"), enquote(QString::fromUtf8(e.name())));
+
+ QList<QPair<QString, QString> > namesValues;
+ for (int index = 0; index < e.keyCount(); ++index) {
+ namesValues.append(qMakePair(enquote(QString::fromUtf8(e.key(index))), QString::number(e.value(index))));
+ }
+
+ qml->writeScriptObjectLiteralBinding(QLatin1String("values"), namesValues);
+ qml->writeEndObject();
+ }
+};
+
+
+enum ExitCode {
+ EXIT_INVALIDARGUMENTS = 1,
+ EXIT_SEGV = 2,
+ EXIT_IMPORTERROR = 3
+};
+
+#ifdef Q_OS_UNIX
+void sigSegvHandler(int) {
+ fprintf(stderr, "Error: SEGV\n");
+ if (!currentProperty.isEmpty())
+ fprintf(stderr, "While processing the property '%s', which probably has uninitialized data.\n", currentProperty.toLatin1().constData());
+ if (!inObjectInstantiation.isEmpty())
+ fprintf(stderr, "While instantiating the object '%s'.\n", inObjectInstantiation.toLatin1().constData());
+ exit(EXIT_SEGV);
+}
+#endif
+
+void printUsage(const QString &appName)
+{
+ qWarning() << qPrintable(QString(
+ "Usage: %1 [-v] [-notrelocatable] module.uri version [module/import/path]\n"
+ " %1 [-v] -path path/to/qmldir/directory [version]\n"
+ " %1 [-v] -builtins\n"
+ "Example: %1 Qt.labs.folderlistmodel 2.0 /home/user/dev/qt-install/imports").arg(
+ appName));
+}
+
+int main(int argc, char *argv[])
+{
+#ifdef Q_OS_UNIX
+ // qmldump may crash, but we don't want any crash handlers to pop up
+ // therefore we intercept the segfault and just exit() ourselves
+ struct sigaction sigAction;
+
+ sigemptyset(&sigAction.sa_mask);
+ sigAction.sa_handler = &sigSegvHandler;
+ sigAction.sa_flags = 0;
+
+ sigaction(SIGSEGV, &sigAction, 0);
+#endif
+
+#ifdef QT_SIMULATOR
+ // Running this application would bring up the Qt Simulator (since it links QtGui), avoid that!
+ QtSimulatorPrivate::SimulatorConnection::createStubInstance();
+#endif
+
+ // don't require a window manager even though we're a QGuiApplication
+ qputenv("QT_QPA_PLATFORM", QByteArrayLiteral("minimal"));
+
+ QGuiApplication app(argc, argv);
+ const QStringList args = app.arguments();
+ const QString appName = QFileInfo(app.applicationFilePath()).baseName();
+ if (args.size() < 2) {
+ printUsage(appName);
+ return EXIT_INVALIDARGUMENTS;
+ }
+
+ QString pluginImportUri;
+ QString pluginImportVersion;
+ bool relocatable = true;
+ enum Action { Uri, Path, Builtins };
+ Action action = Uri;
+ {
+ QStringList positionalArgs;
+ foreach (const QString &arg, args) {
+ if (!arg.startsWith(QLatin1Char('-'))) {
+ positionalArgs.append(arg);
+ continue;
+ }
+
+ if (arg == QLatin1String("--notrelocatable")
+ || arg == QLatin1String("-notrelocatable")) {
+ relocatable = false;
+ } else if (arg == QLatin1String("--path")
+ || arg == QLatin1String("-path")) {
+ action = Path;
+ } else if (arg == QLatin1String("--builtins")
+ || arg == QLatin1String("-builtins")) {
+ action = Builtins;
+ } else if (arg == QLatin1String("-v")) {
+ verbose = true;
+ } else {
+ qWarning() << "Invalid argument: " << arg;
+ return EXIT_INVALIDARGUMENTS;
+ }
+ }
+
+ if (action == Uri) {
+ if (positionalArgs.size() != 3 && positionalArgs.size() != 4) {
+ qWarning() << "Incorrect number of positional arguments";
+ return EXIT_INVALIDARGUMENTS;
+ }
+ pluginImportUri = positionalArgs[1];
+ pluginImportVersion = positionalArgs[2];
+ if (positionalArgs.size() >= 4)
+ pluginImportPath = positionalArgs[3];
+ } else if (action == Path) {
+ if (positionalArgs.size() != 2 && positionalArgs.size() != 3) {
+ qWarning() << "Incorrect number of positional arguments";
+ return EXIT_INVALIDARGUMENTS;
+ }
+ pluginImportPath = QDir::fromNativeSeparators(positionalArgs[1]);
+ if (positionalArgs.size() == 3)
+ pluginImportVersion = positionalArgs[2];
+ } else if (action == Builtins) {
+ if (positionalArgs.size() != 1) {
+ qWarning() << "Incorrect number of positional arguments";
+ return EXIT_INVALIDARGUMENTS;
+ }
+ }
+ }
+
+ QQmlEngine engine;
+ if (!pluginImportPath.isEmpty()) {
+ QDir cur = QDir::current();
+ cur.cd(pluginImportPath);
+ pluginImportPath = cur.absolutePath();
+ QDir::setCurrent(pluginImportPath);
+ engine.addImportPath(pluginImportPath);
+ }
+
+ // load the QtQuick 2 plugin
+ {
+ QByteArray code("import QtQuick 2.0\nQtObject {}");
+ QQmlComponent c(&engine);
+ c.setData(code, QUrl::fromLocalFile(pluginImportPath + "/loadqtquick2.qml"));
+ c.create();
+ if (!c.errors().isEmpty()) {
+ foreach (const QQmlError &error, c.errors())
+ qWarning() << error.toString();
+ return EXIT_IMPORTERROR;
+ }
+ }
+
+ // find all QMetaObjects reachable from the builtin module
+ QSet<const QMetaObject *> defaultReachable = collectReachableMetaObjects(&engine);
+ QList<QQmlType *> defaultTypes = QQmlMetaType::qmlTypes();
+
+ // add some otherwise unreachable QMetaObjects
+ defaultReachable.insert(&QQuickMouseEvent::staticMetaObject);
+ // QQuickKeyEvent, QQuickPinchEvent, QQuickDropEvent are not exported
+
+ // this will hold the meta objects we want to dump information of
+ QSet<const QMetaObject *> metas;
+
+ if (action == Builtins) {
+ metas = defaultReachable;
+ } else {
+ // find a valid QtQuick import
+ QByteArray importCode;
+ QQmlType *qtObjectType = QQmlMetaType::qmlType(&QObject::staticMetaObject);
+ if (!qtObjectType) {
+ qWarning() << "Could not find QtObject type";
+ importCode = QByteArray("import QtQuick 2.0\n");
+ } else {
+ QString module = qtObjectType->qmlTypeName();
+ module = module.mid(0, module.lastIndexOf(QLatin1Char('/')));
+ importCode = QString("import %1 %2.%3\n").arg(module,
+ QString::number(qtObjectType->majorVersion()),
+ QString::number(qtObjectType->minorVersion())).toUtf8();
+ }
+
+ // find all QMetaObjects reachable when the specified module is imported
+ if (action != Path) {
+ importCode += QString("import %0 %1\n").arg(pluginImportUri, pluginImportVersion).toLatin1();
+ } else {
+ // pluginImportVersion can be empty
+ importCode += QString("import \".\" %2\n").arg(pluginImportVersion).toLatin1();
+ }
+
+ // create a component with these imports to make sure the imports are valid
+ // and to populate the declarative meta type system
+ {
+ QByteArray code = importCode;
+ code += "QtObject {}";
+ QQmlComponent c(&engine);
+
+ c.setData(code, QUrl::fromLocalFile(pluginImportPath + "/typelist.qml"));
+ c.create();
+ if (!c.errors().isEmpty()) {
+ foreach (const QQmlError &error, c.errors())
+ qWarning() << error.toString();
+ return EXIT_IMPORTERROR;
+ }
+ }
+
+ QSet<const QMetaObject *> candidates = collectReachableMetaObjects(&engine, defaultTypes);
+ candidates.subtract(defaultReachable);
+
+ // Also eliminate meta objects with the same classname.
+ // This is required because extended objects seem not to share
+ // a single meta object instance.
+ QSet<QByteArray> defaultReachableNames;
+ foreach (const QMetaObject *mo, defaultReachable)
+ defaultReachableNames.insert(QByteArray(mo->className()));
+ foreach (const QMetaObject *mo, candidates) {
+ if (!defaultReachableNames.contains(mo->className()))
+ metas.insert(mo);
+ }
+ }
+
+ // setup static rewrites of type names
+ cppToId.insert("QString", "string");
+ cppToId.insert("QQmlEasingValueType::Type", "Type");
+
+ // start dumping data
+ QByteArray bytes;
+ QmlStreamWriter qml(&bytes);
+
+ qml.writeStartDocument();
+ qml.writeLibraryImport(QLatin1String("QtQuick.tooling"), 1, 1);
+ qml.write(QString("\n"
+ "// This file describes the plugin-supplied types contained in the library.\n"
+ "// It is used for QML tooling purposes only.\n"
+ "//\n"
+ "// This file was auto-generated with the command '%1'.\n"
+ "\n").arg(args.join(QLatin1String(" "))));
+ qml.writeStartObject("Module");
+
+ // put the metaobjects into a map so they are always dumped in the same order
+ QMap<QString, const QMetaObject *> nameToMeta;
+ foreach (const QMetaObject *meta, metas)
+ nameToMeta.insert(convertToId(meta), meta);
+
+ Dumper dumper(&qml);
+ if (relocatable)
+ dumper.setRelocatableModuleUri(pluginImportUri);
+ foreach (const QMetaObject *meta, nameToMeta) {
+ dumper.dump(meta);
+ }
+
+ // define QEasingCurve as an extension of QQmlEasingValueType, this way
+ // properties using the QEasingCurve type get useful type information.
+ if (pluginImportUri.isEmpty())
+ dumper.writeEasingCurve();
+
+ qml.writeEndObject();
+ qml.writeEndDocument();
+
+ std::cout << bytes.constData() << std::flush;
+
+ // workaround to avoid crashes on exit
+ QTimer timer;
+ timer.setSingleShot(true);
+ timer.setInterval(0);
+ QObject::connect(&timer, SIGNAL(timeout()), &app, SLOT(quit()));
+ timer.start();
+
+ return app.exec();
+}
diff --git a/tools/qmlplugindump/qmlplugindump.pro b/tools/qmlplugindump/qmlplugindump.pro
new file mode 100644
index 0000000000..b777e0da75
--- /dev/null
+++ b/tools/qmlplugindump/qmlplugindump.pro
@@ -0,0 +1,15 @@
+QT += qml qml-private quick-private core-private
+
+CONFIG += qpa_minimal_plugin
+
+SOURCES += \
+ main.cpp \
+ qmlstreamwriter.cpp
+
+HEADERS += \
+ qmlstreamwriter.h
+
+OTHER_FILES += Info.plist
+macx: QMAKE_INFO_PLIST = Info.plist
+
+load(qt_tool)
diff --git a/tools/qmlplugindump/qmlstreamwriter.cpp b/tools/qmlplugindump/qmlstreamwriter.cpp
new file mode 100644
index 0000000000..629e30b814
--- /dev/null
+++ b/tools/qmlplugindump/qmlstreamwriter.cpp
@@ -0,0 +1,198 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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, Digia gives you certain additional
+** rights. These rights are described in the Digia 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.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qmlstreamwriter.h"
+
+#include <QtCore/QBuffer>
+#include <QtCore/QStringList>
+
+QmlStreamWriter::QmlStreamWriter(QByteArray *array)
+ : m_indentDepth(0)
+ , m_pendingLineLength(0)
+ , m_maybeOneline(false)
+ , m_stream(new QBuffer(array))
+{
+ m_stream->open(QIODevice::WriteOnly);
+}
+
+void QmlStreamWriter::writeStartDocument()
+{
+}
+
+void QmlStreamWriter::writeEndDocument()
+{
+}
+
+void QmlStreamWriter::writeLibraryImport(const QString &uri, int majorVersion, int minorVersion, const QString &as)
+{
+ m_stream->write(QString("import %1 %2.%3").arg(uri, QString::number(majorVersion), QString::number(minorVersion)).toUtf8());
+ if (!as.isEmpty())
+ m_stream->write(QString(" as %1").arg(as).toUtf8());
+ m_stream->write("\n");
+}
+
+void QmlStreamWriter::writeStartObject(const QString &component)
+{
+ flushPotentialLinesWithNewlines();
+ writeIndent();
+ m_stream->write(QString("%1 {").arg(component).toUtf8());
+ ++m_indentDepth;
+ m_maybeOneline = true;
+}
+
+void QmlStreamWriter::writeEndObject()
+{
+ if (m_maybeOneline && !m_pendingLines.isEmpty()) {
+ --m_indentDepth;
+ for (int i = 0; i < m_pendingLines.size(); ++i) {
+ m_stream->write(" ");
+ m_stream->write(m_pendingLines.at(i).trimmed());
+ if (i != m_pendingLines.size() - 1)
+ m_stream->write(";");
+ }
+ m_stream->write(" }\n");
+ m_pendingLines.clear();
+ m_pendingLineLength = 0;
+ m_maybeOneline = false;
+ } else {
+ flushPotentialLinesWithNewlines();
+ --m_indentDepth;
+ writeIndent();
+ m_stream->write("}\n");
+ }
+}
+
+void QmlStreamWriter::writeScriptBinding(const QString &name, const QString &rhs)
+{
+ writePotentialLine(QString("%1: %2").arg(name, rhs).toUtf8());
+}
+
+void QmlStreamWriter::writeArrayBinding(const QString &name, const QStringList &elements)
+{
+ flushPotentialLinesWithNewlines();
+ writeIndent();
+
+ // try to use a single line
+ QString singleLine;
+ singleLine += QString("%1: [").arg(name);
+ for (int i = 0; i < elements.size(); ++i) {
+ singleLine += elements.at(i);
+ if (i != elements.size() - 1)
+ singleLine += QLatin1String(", ");
+ }
+ singleLine += QLatin1String("]\n");
+ if (singleLine.size() + m_indentDepth * 4 < 80) {
+ m_stream->write(singleLine.toUtf8());
+ return;
+ }
+
+ // write multi-line
+ m_stream->write(QString("%1: [\n").arg(name).toUtf8());
+ ++m_indentDepth;
+ for (int i = 0; i < elements.size(); ++i) {
+ writeIndent();
+ m_stream->write(elements.at(i).toUtf8());
+ if (i != elements.size() - 1) {
+ m_stream->write(",\n");
+ } else {
+ m_stream->write("\n");
+ }
+ }
+ --m_indentDepth;
+ writeIndent();
+ m_stream->write("]\n");
+}
+
+void QmlStreamWriter::write(const QString &data)
+{
+ flushPotentialLinesWithNewlines();
+ m_stream->write(data.toUtf8());
+}
+
+void QmlStreamWriter::writeScriptObjectLiteralBinding(const QString &name, const QList<QPair<QString, QString> > &keyValue)
+{
+ flushPotentialLinesWithNewlines();
+ writeIndent();
+ m_stream->write(QString("%1: {\n").arg(name).toUtf8());
+ ++m_indentDepth;
+ for (int i = 0; i < keyValue.size(); ++i) {
+ const QString key = keyValue.at(i).first;
+ const QString value = keyValue.at(i).second;
+ writeIndent();
+ m_stream->write(QString("%1: %2").arg(key, value).toUtf8());
+ if (i != keyValue.size() - 1) {
+ m_stream->write(",\n");
+ } else {
+ m_stream->write("\n");
+ }
+ }
+ --m_indentDepth;
+ writeIndent();
+ m_stream->write("}\n");
+}
+
+void QmlStreamWriter::writeIndent()
+{
+ m_stream->write(QByteArray(m_indentDepth * 4, ' '));
+}
+
+void QmlStreamWriter::writePotentialLine(const QByteArray &line)
+{
+ m_pendingLines.append(line);
+ m_pendingLineLength += line.size();
+ if (m_pendingLineLength >= 80) {
+ flushPotentialLinesWithNewlines();
+ }
+}
+
+void QmlStreamWriter::flushPotentialLinesWithNewlines()
+{
+ if (m_maybeOneline)
+ m_stream->write("\n");
+ foreach (const QByteArray &line, m_pendingLines) {
+ writeIndent();
+ m_stream->write(line);
+ m_stream->write("\n");
+ }
+ m_pendingLines.clear();
+ m_pendingLineLength = 0;
+ m_maybeOneline = false;
+}
diff --git a/tools/qmlplugindump/qmlstreamwriter.h b/tools/qmlplugindump/qmlstreamwriter.h
new file mode 100644
index 0000000000..9d8052911c
--- /dev/null
+++ b/tools/qmlplugindump/qmlstreamwriter.h
@@ -0,0 +1,79 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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, Digia gives you certain additional
+** rights. These rights are described in the Digia 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.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QMLSTREAMWRITER_H
+#define QMLSTREAMWRITER_H
+
+#include <QtCore/QIODevice>
+#include <QtCore/QList>
+#include <QtCore/QString>
+#include <QtCore/QScopedPointer>
+#include <QtCore/QPair>
+
+class QmlStreamWriter
+{
+public:
+ QmlStreamWriter(QByteArray *array);
+
+ void writeStartDocument();
+ void writeEndDocument();
+ void writeLibraryImport(const QString &uri, int majorVersion, int minorVersion, const QString &as = QString());
+ //void writeFilesystemImport(const QString &file, const QString &as = QString());
+ void writeStartObject(const QString &component);
+ void writeEndObject();
+ void writeScriptBinding(const QString &name, const QString &rhs);
+ void writeScriptObjectLiteralBinding(const QString &name, const QList<QPair<QString, QString> > &keyValue);
+ void writeArrayBinding(const QString &name, const QStringList &elements);
+ void write(const QString &data);
+
+private:
+ void writeIndent();
+ void writePotentialLine(const QByteArray &line);
+ void flushPotentialLinesWithNewlines();
+
+ int m_indentDepth;
+ QList<QByteArray> m_pendingLines;
+ int m_pendingLineLength;
+ bool m_maybeOneline;
+ QScopedPointer<QIODevice> m_stream;
+};
+
+#endif // QMLSTREAMWRITER_H
diff --git a/tools/qmlprofiler/commandlistener.cpp b/tools/qmlprofiler/commandlistener.cpp
new file mode 100644
index 0000000000..d6eb8c6072
--- /dev/null
+++ b/tools/qmlprofiler/commandlistener.cpp
@@ -0,0 +1,66 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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, Digia gives you certain additional
+** rights. These rights are described in the Digia 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.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "commandlistener.h"
+#include "constants.h"
+#include <QtCore/QTextStream>
+
+CommandListener::CommandListener(QObject *parent)
+ : QThread(parent)
+ , m_stopRequested(false)
+{
+}
+
+void CommandListener::run()
+{
+ QString line;
+ QTextStream in(stdin, QIODevice::ReadOnly);
+ do {
+ line = in.readLine();
+ line = line.trimmed();
+ if (!line.isEmpty()) {
+ emit command(line);
+ if (line == QLatin1String(Constants::CMD_QUIT)
+ || line == QLatin1String(Constants::CMD_QUIT2))
+ return;
+ }
+ } while (!m_stopRequested && !line.isNull());
+}
diff --git a/tools/qmlprofiler/commandlistener.h b/tools/qmlprofiler/commandlistener.h
new file mode 100644
index 0000000000..cb4f3d4762
--- /dev/null
+++ b/tools/qmlprofiler/commandlistener.h
@@ -0,0 +1,63 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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, Digia gives you certain additional
+** rights. These rights are described in the Digia 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.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef COMMANDLISTENER_H
+#define COMMANDLISTENER_H
+
+#include <QtCore/QThread>
+
+class CommandListener : public QThread
+{
+ Q_OBJECT
+public:
+ CommandListener(QObject *parent = 0);
+
+ void run();
+
+ void requestStop() { m_stopRequested = true; }
+signals:
+ void command(const QString &command);
+
+private:
+ bool m_stopRequested;
+};
+
+#endif // COMMANDLISTENER_H
diff --git a/tools/qmlprofiler/constants.h b/tools/qmlprofiler/constants.h
new file mode 100644
index 0000000000..0adebdc48d
--- /dev/null
+++ b/tools/qmlprofiler/constants.h
@@ -0,0 +1,59 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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, Digia gives you certain additional
+** rights. These rights are described in the Digia 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.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef CONSTANTS_H
+#define CONSTANTS_H
+
+namespace Constants {
+
+const char CMD_HELP[] ="help";
+const char CMD_HELP2[] = "h";
+const char CMD_HELP3[] = "?";
+
+const char CMD_RECORD[] ="record";
+const char CMD_RECORD2[] ="r";
+
+const char CMD_QUIT[] ="quit";
+const char CMD_QUIT2[] = "q";
+
+} // Constants
+
+#endif // CONSTANTS_H
diff --git a/tools/qmlprofiler/main.cpp b/tools/qmlprofiler/main.cpp
new file mode 100644
index 0000000000..39ef215595
--- /dev/null
+++ b/tools/qmlprofiler/main.cpp
@@ -0,0 +1,64 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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, Digia gives you certain additional
+** rights. These rights are described in the Digia 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.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "commandlistener.h"
+#include "qmlprofilerapplication.h"
+
+int main(int argc, char *argv[])
+{
+ QmlProfilerApplication app(argc, argv);
+
+ if (!app.parseArguments()) {
+ app.printUsage();
+ return 1;
+ }
+
+ CommandListener listener;
+ QObject::connect(&listener, SIGNAL(command(QString)), &app, SLOT(userCommand(QString)));
+ listener.start();
+
+ int exitValue = app.exec();
+ // wait for listener to exit
+ listener.wait();
+
+
+ return exitValue;
+}
diff --git a/tools/qmlprofiler/qmlprofiler.pro b/tools/qmlprofiler/qmlprofiler.pro
new file mode 100644
index 0000000000..c5def993f3
--- /dev/null
+++ b/tools/qmlprofiler/qmlprofiler.pro
@@ -0,0 +1,21 @@
+QT += qml qml-private v8-private network core-private
+
+SOURCES += main.cpp \
+ qmlprofilerapplication.cpp \
+ commandlistener.cpp \
+ qqmldebugclient.cpp \
+ qmlprofilerdata.cpp \
+ qmlprofilerclient.cpp \
+ qpacketprotocol.cpp
+
+HEADERS += \
+ qmlprofilerapplication.h \
+ commandlistener.h \
+ constants.h \
+ qmlprofilerdata.h \
+ qmlprofilerclient.h \
+ qmlprofilereventlocation.h \
+ qqmldebugclient.h \
+ qpacketprotocol.h
+
+load(qt_tool)
diff --git a/tools/qmlprofiler/qmlprofilerapplication.cpp b/tools/qmlprofiler/qmlprofilerapplication.cpp
new file mode 100644
index 0000000000..6c3e697f56
--- /dev/null
+++ b/tools/qmlprofiler/qmlprofilerapplication.cpp
@@ -0,0 +1,434 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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, Digia gives you certain additional
+** rights. These rights are described in the Digia 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.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qmlprofilerapplication.h"
+#include "constants.h"
+#include <QtCore/QStringList>
+#include <QtCore/QTextStream>
+#include <QtCore/QProcess>
+#include <QtCore/QTimer>
+#include <QtCore/QDateTime>
+#include <QtCore/QFileInfo>
+#include <QtCore/QDebug>
+
+static const char usageTextC[] =
+"Usage:\n"
+" qmlprofiler [options] [program] [program-options]\n"
+" qmlprofiler [options] -attach [hostname]\n"
+"\n"
+"QML Profiler retrieves QML tracing data from a running application.\n"
+"The data collected can then be visualized in Qt Creator.\n"
+"\n"
+"The application to be profiled has to enable QML debugging. See the Qt Creator\n"
+"documentation on how to do this for different Qt versions.\n"
+"\n"
+"Options:\n"
+" -help Show this information and exit.\n"
+" -fromStart\n"
+" Record as soon as the engine is started, default is false.\n"
+" -p <number>, -port <number>\n"
+" TCP/IP port to use, default is 3768.\n"
+" -v, -verbose\n"
+" Print debugging output.\n"
+" -version\n"
+" Show the version of qmlprofiler and exit.\n";
+
+static const char commandTextC[] =
+"Commands:\n"
+" r, record\n"
+" Switch recording on or off.\n"
+" q, quit\n"
+" Terminate program.";
+
+static const char TraceFileExtension[] = ".qtd";
+
+QmlProfilerApplication::QmlProfilerApplication(int &argc, char **argv) :
+ QCoreApplication(argc, argv),
+ m_runMode(LaunchMode),
+ m_process(0),
+ m_tracePrefix(QLatin1String("trace")),
+ m_hostName(QLatin1String("127.0.0.1")),
+ m_port(3768),
+ m_verbose(false),
+ m_quitAfterSave(false),
+ m_qmlProfilerClient(&m_connection),
+ m_v8profilerClient(&m_connection),
+ m_connectionAttempts(0),
+ m_qmlDataReady(false),
+ m_v8DataReady(false)
+{
+ m_connectTimer.setInterval(1000);
+ connect(&m_connectTimer, SIGNAL(timeout()), this, SLOT(tryToConnect()));
+
+ connect(&m_connection, SIGNAL(connected()), this, SLOT(connected()));
+ connect(&m_connection, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(connectionStateChanged(QAbstractSocket::SocketState)));
+ connect(&m_connection, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(connectionError(QAbstractSocket::SocketError)));
+
+ connect(&m_qmlProfilerClient, SIGNAL(enabledChanged()), this, SLOT(traceClientEnabled()));
+ connect(&m_qmlProfilerClient, SIGNAL(recordingChanged(bool)), this, SLOT(recordingChanged()));
+ connect(&m_qmlProfilerClient, SIGNAL(range(QQmlProfilerService::RangeType,QQmlProfilerService::BindingType,qint64,qint64,QStringList,QmlEventLocation)),
+ &m_profilerData, SLOT(addQmlEvent(QQmlProfilerService::RangeType,QQmlProfilerService::BindingType,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_profilerData, SLOT(addV8Event(int,QString,QString,int,double,double)));
+ connect(&m_v8profilerClient, SIGNAL(complete()), this, SLOT(v8Complete()));
+
+ connect(&m_profilerData, SIGNAL(error(QString)), this, SLOT(logError(QString)));
+ connect(&m_profilerData, SIGNAL(dataReady()), this, SLOT(traceFinished()));
+
+}
+
+QmlProfilerApplication::~QmlProfilerApplication()
+{
+ if (!m_process)
+ return;
+ logStatus("Terminating process ...");
+ m_process->disconnect();
+ m_process->terminate();
+ if (!m_process->waitForFinished(1000)) {
+ logStatus("Killing process ...");
+ m_process->kill();
+ }
+ delete m_process;
+}
+
+bool QmlProfilerApplication::parseArguments()
+{
+ for (int argPos = 1; argPos < arguments().size(); ++argPos) {
+ const QString arg = arguments().at(argPos);
+ if (arg == QLatin1String("-attach") || arg == QLatin1String("-a")) {
+ if (argPos + 1 == arguments().size()) {
+ return false;
+ }
+ m_hostName = arguments().at(++argPos);
+ m_runMode = AttachMode;
+ } else if (arg == QLatin1String("-port") || arg == QLatin1String("-p")) {
+ if (argPos + 1 == arguments().size()) {
+ return false;
+ }
+ const QString portStr = arguments().at(++argPos);
+ bool isNumber;
+ m_port = portStr.toUShort(&isNumber);
+ if (!isNumber) {
+ logError(QString("'%1' is not a valid port").arg(portStr));
+ return false;
+ }
+ } else if (arg == QLatin1String("-fromStart")) {
+ m_qmlProfilerClient.setRecording(true);
+ m_v8profilerClient.setRecording(true);
+ } else if (arg == QLatin1String("-help") || arg == QLatin1String("-h") || arg == QLatin1String("/h") || arg == QLatin1String("/?")) {
+ return false;
+ } else if (arg == QLatin1String("-verbose") || arg == QLatin1String("-v")) {
+ m_verbose = true;
+ } else if (arg == QLatin1String("-version")) {
+ print(QString("QML Profiler based on Qt %1.").arg(qVersion()));
+ ::exit(1);
+ return false;
+ } else {
+ if (m_programPath.isEmpty()) {
+ m_programPath = arg;
+ m_tracePrefix = QFileInfo(m_programPath).fileName();
+ } else {
+ m_programArguments << arg;
+ }
+ }
+ }
+
+ if (m_runMode == LaunchMode
+ && m_programPath.isEmpty())
+ return false;
+
+ if (m_runMode == AttachMode
+ && !m_programPath.isEmpty())
+ return false;
+
+ return true;
+}
+
+void QmlProfilerApplication::printUsage()
+{
+ print(QLatin1String(usageTextC));
+ print(QLatin1String(commandTextC));
+}
+
+int QmlProfilerApplication::exec()
+{
+ QTimer::singleShot(0, this, SLOT(run()));
+ return QCoreApplication::exec();
+}
+
+void QmlProfilerApplication::printCommands()
+{
+ print(QLatin1String(commandTextC));
+}
+
+QString QmlProfilerApplication::traceFileName() const
+{
+ QString fileName = m_tracePrefix + "_" +
+ QDateTime::currentDateTime().toString(QLatin1String("yyMMdd_hhmmss")) +
+ TraceFileExtension;
+ if (QFileInfo(fileName).exists()) {
+ QString baseName;
+ int suffixIndex = 0;
+ do {
+ baseName = QFileInfo(fileName).baseName()
+ + QString::number(suffixIndex++);
+ } while (QFileInfo(baseName + TraceFileExtension).exists());
+ fileName = baseName + TraceFileExtension;
+ }
+
+ return QFileInfo(fileName).absoluteFilePath();
+}
+
+void QmlProfilerApplication::userCommand(const QString &command)
+{
+ QString cmd = command.trimmed();
+ if (cmd == Constants::CMD_HELP
+ || cmd == Constants::CMD_HELP2
+ || cmd == Constants::CMD_HELP3) {
+ printCommands();
+ } else if (cmd == Constants::CMD_RECORD
+ || cmd == Constants::CMD_RECORD2) {
+ m_qmlProfilerClient.setRecording(
+ !m_qmlProfilerClient.isRecording());
+ m_v8profilerClient.setRecording(!m_v8profilerClient.isRecording());
+ m_qmlDataReady = false;
+ m_v8DataReady = false;
+ } else if (cmd == Constants::CMD_QUIT
+ || cmd == Constants::CMD_QUIT2) {
+ print(QLatin1String("Quit"));
+ if (m_qmlProfilerClient.isRecording()) {
+ m_quitAfterSave = true;
+ m_qmlDataReady = false;
+ m_v8DataReady = false;
+ m_qmlProfilerClient.setRecording(false);
+ m_v8profilerClient.setRecording(false);
+ } else {
+ quit();
+ }
+ }
+}
+
+void QmlProfilerApplication::run()
+{
+ if (m_runMode == LaunchMode) {
+ m_process = new QProcess(this);
+ QStringList arguments;
+ arguments << QString::fromLatin1("-qmljsdebugger=port:%1,block").arg(m_port);
+ arguments << m_programArguments;
+
+ m_process->setProcessChannelMode(QProcess::MergedChannels);
+ connect(m_process, SIGNAL(readyRead()), this, SLOT(processHasOutput()));
+ connect(m_process, SIGNAL(finished(int,QProcess::ExitStatus)), this,
+ SLOT(processFinished()));
+ logStatus(QString("Starting '%1 %2' ...").arg(m_programPath,
+ arguments.join(" ")));
+ m_process->start(m_programPath, arguments);
+ if (!m_process->waitForStarted()) {
+ logError(QString("Could not run '%1': %2").arg(m_programPath,
+ m_process->errorString()));
+ exit(1);
+ }
+
+ }
+ m_connectTimer.start();
+}
+
+void QmlProfilerApplication::tryToConnect()
+{
+ Q_ASSERT(!m_connection.isConnected());
+ ++ m_connectionAttempts;
+
+ if (!m_verbose && !(m_connectionAttempts % 5)) {// print every 5 seconds
+ if (!m_verbose)
+ logError(QString("Could not connect to %1:%2 for %3 seconds ...").arg(
+ m_hostName, QString::number(m_port),
+ QString::number(m_connectionAttempts)));
+ }
+
+ if (m_connection.state() == QAbstractSocket::UnconnectedState) {
+ logStatus(QString("Connecting to %1:%2 ...").arg(m_hostName,
+ QString::number(m_port)));
+ m_connection.connectToHost(m_hostName, m_port);
+ }
+}
+
+void QmlProfilerApplication::connected()
+{
+ m_connectTimer.stop();
+ print(QString(QLatin1String("Connected to host:port %1:%2."
+ "Wait for profile data or type a command"
+ "(type 'help'' to show list of commands).")
+ ).arg(m_hostName).arg((m_port)));
+ QString recordingStatus(QLatin1String("Recording Status: %1"));
+ if (!m_qmlProfilerClient.isRecording() &&
+ !m_v8profilerClient.isRecording())
+ recordingStatus = recordingStatus.arg(QLatin1String("Off"));
+ else
+ recordingStatus = recordingStatus.arg(QLatin1String("On"));
+ print(recordingStatus);
+}
+
+void QmlProfilerApplication::connectionStateChanged(
+ QAbstractSocket::SocketState state)
+{
+ if (m_verbose)
+ qDebug() << state;
+}
+
+void QmlProfilerApplication::connectionError(QAbstractSocket::SocketError error)
+{
+ if (m_verbose)
+ qDebug() << error;
+}
+
+void QmlProfilerApplication::processHasOutput()
+{
+ Q_ASSERT(m_process);
+ while (m_process->bytesAvailable()) {
+ QTextStream out(stdout);
+ out << m_process->readAll();
+ }
+}
+
+void QmlProfilerApplication::processFinished()
+{
+ Q_ASSERT(m_process);
+ if (m_process->exitStatus() == QProcess::NormalExit) {
+ logStatus(QString("Process exited (%1).").arg(m_process->exitCode()));
+
+ if (m_qmlProfilerClient.isRecording()) {
+ logError("Process exited while recording, last trace is lost!");
+ exit(2);
+ } else {
+ exit(0);
+ }
+ } else {
+ logError("Process crashed! Exiting ...");
+ exit(3);
+ }
+}
+
+void QmlProfilerApplication::traceClientEnabled()
+{
+ logStatus("Trace client is attached.");
+ // blocked server is waiting for recording message from both clients
+ // once the last one is connected, both messages should be sent
+ m_qmlProfilerClient.sendRecordingStatus();
+ m_v8profilerClient.sendRecordingStatus();
+}
+
+void QmlProfilerApplication::profilerClientEnabled()
+{
+ logStatus("Profiler client is attached.");
+
+ // blocked server is waiting for recording message from both clients
+ // once the last one is connected, both messages should be sent
+ m_qmlProfilerClient.sendRecordingStatus();
+ m_v8profilerClient.sendRecordingStatus();
+}
+
+void QmlProfilerApplication::traceFinished()
+{
+ const QString fileName = traceFileName();
+
+ if (m_profilerData.save(fileName))
+ print(QString("Saving trace to %1.").arg(fileName));
+
+ if (m_quitAfterSave)
+ quit();
+}
+
+void QmlProfilerApplication::recordingChanged()
+{
+ if (m_qmlProfilerClient.isRecording()) {
+ print(QLatin1String("Recording is on."));
+ } else {
+ print(QLatin1String("Recording is off."));
+ }
+}
+
+void QmlProfilerApplication::print(const QString &line)
+{
+ QTextStream err(stderr);
+ err << line << endl;
+}
+
+void QmlProfilerApplication::logError(const QString &error)
+{
+ QTextStream err(stderr);
+ err << "Error: " << error << endl;
+}
+
+void QmlProfilerApplication::logStatus(const QString &status)
+{
+ if (!m_verbose)
+ return;
+ QTextStream err(stderr);
+ err << status << endl;
+}
+
+void QmlProfilerApplication::qmlComplete()
+{
+ m_qmlDataReady = true;
+ if (m_v8profilerClient.state() != QQmlDebugClient::Enabled ||
+ m_v8DataReady) {
+ m_profilerData.complete();
+ // once complete is sent, reset the flag
+ m_qmlDataReady = false;
+ }
+}
+
+void QmlProfilerApplication::v8Complete()
+{
+ m_v8DataReady = true;
+ if (m_qmlProfilerClient.state() != QQmlDebugClient::Enabled ||
+ m_qmlDataReady) {
+ 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
new file mode 100644
index 0000000000..23967eede6
--- /dev/null
+++ b/tools/qmlprofiler/qmlprofilerapplication.h
@@ -0,0 +1,118 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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, Digia gives you certain additional
+** rights. These rights are described in the Digia 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.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QMLPROFILERAPPLICATION_H
+#define QMLPROFILERAPPLICATION_H
+
+#include <QtCore/QCoreApplication>
+#include <QtCore/QProcess>
+#include <QtCore/QTimer>
+
+#include "qmlprofilerclient.h"
+#include "qmlprofilerdata.h"
+
+class QmlProfilerApplication : public QCoreApplication
+{
+ Q_OBJECT
+public:
+ QmlProfilerApplication(int &argc, char **argv);
+ ~QmlProfilerApplication();
+
+ bool parseArguments();
+ void printUsage();
+ int exec();
+
+public slots:
+ void userCommand(const QString &command);
+
+private slots:
+ void run();
+ void tryToConnect();
+ void connected();
+ void connectionStateChanged(QAbstractSocket::SocketState state);
+ void connectionError(QAbstractSocket::SocketError error);
+ void processHasOutput();
+ void processFinished();
+
+ void traceClientEnabled();
+ void profilerClientEnabled();
+ void traceFinished();
+ void recordingChanged();
+
+ void print(const QString &line);
+ void logError(const QString &error);
+ void logStatus(const QString &status);
+
+ void qmlComplete();
+ void v8Complete();
+
+private:
+ void printCommands();
+ QString traceFileName() const;
+
+ enum ApplicationMode {
+ LaunchMode,
+ AttachMode
+ } m_runMode;
+
+ // LaunchMode
+ QString m_programPath;
+ QStringList m_programArguments;
+ QProcess *m_process;
+ QString m_tracePrefix;
+
+ QString m_hostName;
+ quint16 m_port;
+ bool m_verbose;
+ bool m_quitAfterSave;
+
+ QQmlDebugConnection m_connection;
+ QmlProfilerClient m_qmlProfilerClient;
+ V8ProfilerClient m_v8profilerClient;
+ QmlProfilerData m_profilerData;
+ QTimer m_connectTimer;
+ uint m_connectionAttempts;
+
+ bool m_qmlDataReady;
+ bool m_v8DataReady;
+};
+
+#endif // QMLPROFILERAPPLICATION_H
diff --git a/tools/qmlprofiler/qmlprofilerclient.cpp b/tools/qmlprofiler/qmlprofilerclient.cpp
new file mode 100644
index 0000000000..25557af77f
--- /dev/null
+++ b/tools/qmlprofiler/qmlprofilerclient.cpp
@@ -0,0 +1,313 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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, Digia gives you certain additional
+** rights. These rights are described in the Digia 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.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qmlprofilerclient.h"
+
+#include <QtCore/QStack>
+#include <QtCore/QStringList>
+
+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<qint64> rangeStartTimes[QQmlProfilerService::MaximumRangeType];
+ QStack<QStringList> rangeDatas[QQmlProfilerService::MaximumRangeType];
+ QStack<QmlEventLocation> rangeLocations[QQmlProfilerService::MaximumRangeType];
+ QStack<QQmlProfilerService::BindingType> bindingTypes;
+ 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));
+ d->bindingTypes.clear();
+ 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<qint64>(1) << range);
+ ++d->rangeCount[range];
+
+ // read binding type
+ if (range == (int)QQmlProfilerService::Binding) {
+ int bindingType = (int)QQmlProfilerService::QmlBinding;
+ if (!stream.atEnd())
+ stream >> bindingType;
+ d->bindingTypes.push((QQmlProfilerService::BindingType)bindingType);
+ }
+ } 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<qint64>(1) << range))
+ d->inProgressRanges &= ~(static_cast<qint64>(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();
+ QQmlProfilerService::BindingType bindingType = QQmlProfilerService::QmlBinding;
+ if (range == (int)QQmlProfilerService::Binding)
+ bindingType = d->bindingTypes.pop();
+ emit this->range((QQmlProfilerService::RangeType)range,
+ bindingType, 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..e0bba0b660
--- /dev/null
+++ b/tools/qmlprofiler/qmlprofilerclient.h
@@ -0,0 +1,140 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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, Digia gives you certain additional
+** rights. These rights are described in the Digia 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.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QMLPROFILERCLIENT_H
+#define QMLPROFILERCLIENT_H
+
+#include "qqmldebugclient.h"
+#include <QtQml/private/qqmlprofilerservice_p.h>
+#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,
+ QQmlProfilerService::BindingType bindingType,
+ 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..5d387d6234
--- /dev/null
+++ b/tools/qmlprofiler/qmlprofilerdata.cpp
@@ -0,0 +1,607 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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, Digia gives you certain additional
+** rights. These rights are described in the Digia 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.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qmlprofilerdata.h"
+
+#include <QStringList>
+#include <QUrl>
+#include <QHash>
+#include <QFile>
+#include <QXmlStreamReader>
+
+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 QQmlProfilerService::BindingType &_bindingType,
+ const QString &_eventHashStr,
+ const QmlEventLocation &_location,
+ const QString &_details,
+ const QQmlProfilerService::RangeType &_eventType)
+ : displayName(_displayName),eventHashStr(_eventHashStr),location(_location),
+ details(_details),eventType(_eventType),bindingType(_bindingType) {}
+ QString displayName;
+ QString eventHashStr;
+ QmlEventLocation location;
+ QString details;
+ QQmlProfilerService::RangeType eventType;
+ QQmlProfilerService::BindingType bindingType;
+};
+
+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<QString, qint64> v8children;
+};
+
+/////////////////////////////////////////////////////////////////
+class QmlProfilerDataPrivate
+{
+public:
+ QmlProfilerDataPrivate(QmlProfilerData *qq){ Q_UNUSED(qq); }
+
+ // data storage
+ QHash<QString, QmlRangeEventData *> eventDescriptions;
+ QVector<QmlRangeEventStartInstance> startInstanceList;
+ QHash<QString, QV8EventInfo *> v8EventHash;
+
+ qint64 traceStartTime;
+ qint64 traceEndTime;
+
+ // internal state while collecting events
+ QmlRangeEventStartInstance *lastFrameEvent;
+ qint64 qmlMeasuredTime;
+ qint64 v8MeasuredTime;
+ QHash<int, QV8EventInfo *> 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,
+ QQmlProfilerService::BindingType bindingType,
+ 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("<bytecode>");
+ 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, bindingType, 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("<Animation Update>");
+ QString eventHashStr = displayName;
+
+ QmlRangeEventData *newEvent;
+ if (d->eventDescriptions.contains(eventHashStr)) {
+ newEvent = d->eventDescriptions[eventHashStr];
+ } else {
+ newEvent = new QmlRangeEventData(displayName, QQmlProfilerService::QmlBinding, 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("<program>");
+}
+
+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<int, qint64> 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<QmlRangeEventStartInstance>::iterator itFrom = d->startInstanceList.end() - 2;
+ QVector<QmlRangeEventStartInstance>::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);
+ if (eventData->eventType == QQmlProfilerService::Binding)
+ stream.writeTextElement(QStringLiteral("bindingType"), QString::number((int)eventData->bindingType));
+ 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..134e7228af
--- /dev/null
+++ b/tools/qmlprofiler/qmlprofilerdata.h
@@ -0,0 +1,106 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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, Digia gives you certain additional
+** rights. These rights are described in the Digia 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.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QMLPROFILERDATA_H
+#define QMLPROFILERDATA_H
+
+#include <QtQml/private/qqmlprofilerservice_p.h>
+#include "qmlprofilereventlocation.h"
+
+#include <QObject>
+
+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,
+ QQmlProfilerService::BindingType bindingType,
+ 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..b97996dbe3
--- /dev/null
+++ b/tools/qmlprofiler/qmlprofilereventlocation.h
@@ -0,0 +1,61 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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, Digia gives you certain additional
+** rights. These rights are described in the Digia 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.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QMLPROFILEREVENTLOCATION_H
+#define QMLPROFILEREVENTLOCATION_H
+
+#include <QString>
+
+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
diff --git a/tools/qmlprofiler/qpacketprotocol.cpp b/tools/qmlprofiler/qpacketprotocol.cpp
new file mode 100644
index 0000000000..b0cb289c10
--- /dev/null
+++ b/tools/qmlprofiler/qpacketprotocol.cpp
@@ -0,0 +1,546 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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, Digia gives you certain additional
+** rights. These rights are described in the Digia 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.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qpacketprotocol.h"
+
+#include <QtCore/QBuffer>
+#include <QtCore/QElapsedTimer>
+
+static const unsigned int MAX_PACKET_SIZE = 0x7FFFFFFF;
+
+/*!
+ \class QPacketProtocol
+ \internal
+
+ \brief The QPacketProtocol class encapsulates communicating discrete packets
+ across fragmented IO channels, such as TCP sockets.
+
+ QPacketProtocol makes it simple to send arbitrary sized data "packets" across
+ fragmented transports such as TCP and UDP.
+
+ As transmission boundaries are not respected, sending packets over protocols
+ like TCP frequently involves "stitching" them back together at the receiver.
+ QPacketProtocol makes this easier by performing this task for you. Packet
+ data sent using QPacketProtocol is prepended with a 4-byte size header
+ allowing the receiving QPacketProtocol to buffer the packet internally until
+ it has all been received. QPacketProtocol does not perform any sanity
+ checking on the size or on the data, so this class should only be used in
+ prototyping or trusted situations where DOS attacks are unlikely.
+
+ QPacketProtocol does not perform any communications itself. Instead it can
+ operate on any QIODevice that supports the QIODevice::readyRead() signal. A
+ logical "packet" is encapsulated by the companion QPacket class. The
+ following example shows two ways to send data using QPacketProtocol. The
+ transmitted data is equivalent in both.
+
+ \code
+ QTcpSocket socket;
+ // ... connect socket ...
+
+ QPacketProtocol protocol(&socket);
+
+ // Send packet the quick way
+ protocol.send() << "Hello world" << 123;
+
+ // Send packet the longer way
+ QPacket packet;
+ packet << "Hello world" << 123;
+ protocol.send(packet);
+ \endcode
+
+ Likewise, the following shows how to read data from QPacketProtocol, assuming
+ that the QPacketProtocol::readyRead() signal has been emitted.
+
+ \code
+ // ... QPacketProtocol::readyRead() is emitted ...
+
+ int a;
+ QByteArray b;
+
+ // Receive packet the quick way
+ protocol.read() >> a >> b;
+
+ // Receive packet the longer way
+ QPacket packet = protocol.read();
+ p >> a >> b;
+ \endcode
+
+ \ingroup io
+ \sa QPacket
+*/
+
+class QPacketProtocolPrivate : public QObject
+{
+ Q_OBJECT
+public:
+ QPacketProtocolPrivate(QPacketProtocol *parent, QIODevice *_dev)
+ : QObject(parent), inProgressSize(-1), maxPacketSize(MAX_PACKET_SIZE),
+ waitingForPacket(false), dev(_dev)
+ {
+ Q_ASSERT(4 == sizeof(qint32));
+
+ QObject::connect(this, SIGNAL(readyRead()),
+ parent, SIGNAL(readyRead()));
+ QObject::connect(this, SIGNAL(packetWritten()),
+ parent, SIGNAL(packetWritten()));
+ QObject::connect(this, SIGNAL(invalidPacket()),
+ parent, SIGNAL(invalidPacket()));
+ QObject::connect(dev, SIGNAL(readyRead()),
+ this, SLOT(readyToRead()));
+ QObject::connect(dev, SIGNAL(aboutToClose()),
+ this, SLOT(aboutToClose()));
+ QObject::connect(dev, SIGNAL(bytesWritten(qint64)),
+ this, SLOT(bytesWritten(qint64)));
+ }
+
+Q_SIGNALS:
+ void readyRead();
+ void packetWritten();
+ void invalidPacket();
+
+public Q_SLOTS:
+ void aboutToClose()
+ {
+ inProgress.clear();
+ sendingPackets.clear();
+ inProgressSize = -1;
+ }
+
+ void bytesWritten(qint64 bytes)
+ {
+ Q_ASSERT(!sendingPackets.isEmpty());
+
+ while (bytes) {
+ if (sendingPackets.at(0) > bytes) {
+ sendingPackets[0] -= bytes;
+ bytes = 0;
+ } else {
+ bytes -= sendingPackets.at(0);
+ sendingPackets.removeFirst();
+ emit packetWritten();
+ }
+ }
+ }
+
+ void readyToRead()
+ {
+ while (true) {
+ // Need to get trailing data
+ if (-1 == inProgressSize) {
+ // We need a size header of sizeof(qint32)
+ if (sizeof(qint32) > (uint)dev->bytesAvailable())
+ return;
+
+ // Read size header
+ int read = dev->read((char *)&inProgressSize, sizeof(qint32));
+ Q_ASSERT(read == sizeof(qint32));
+ Q_UNUSED(read);
+
+ // Check sizing constraints
+ if (inProgressSize > maxPacketSize) {
+ QObject::disconnect(dev, SIGNAL(readyRead()),
+ this, SLOT(readyToRead()));
+ QObject::disconnect(dev, SIGNAL(aboutToClose()),
+ this, SLOT(aboutToClose()));
+ QObject::disconnect(dev, SIGNAL(bytesWritten(qint64)),
+ this, SLOT(bytesWritten(qint64)));
+ dev = 0;
+ emit invalidPacket();
+ return;
+ }
+
+ inProgressSize -= sizeof(qint32);
+ } else {
+ inProgress.append(dev->read(inProgressSize - inProgress.size()));
+
+ if (inProgressSize == inProgress.size()) {
+ // Packet has arrived!
+ packets.append(inProgress);
+ inProgressSize = -1;
+ inProgress.clear();
+
+ waitingForPacket = false;
+ emit readyRead();
+ } else
+ return;
+ }
+ }
+ }
+
+public:
+ QList<qint64> sendingPackets;
+ QList<QByteArray> packets;
+ QByteArray inProgress;
+ qint32 inProgressSize;
+ qint32 maxPacketSize;
+ bool waitingForPacket;
+ QIODevice *dev;
+};
+
+/*!
+ Construct a QPacketProtocol instance that works on \a dev with the
+ specified \a parent.
+ */
+QPacketProtocol::QPacketProtocol(QIODevice *dev, QObject *parent)
+ : QObject(parent), d(new QPacketProtocolPrivate(this, dev))
+{
+ Q_ASSERT(dev);
+}
+
+/*!
+ Destroys the QPacketProtocol instance.
+ */
+QPacketProtocol::~QPacketProtocol()
+{
+}
+
+/*!
+ Returns the maximum packet size allowed. By default this is
+ 2,147,483,647 bytes.
+
+ If a packet claiming to be larger than the maximum packet size is received,
+ the QPacketProtocol::invalidPacket() signal is emitted.
+
+ \sa QPacketProtocol::setMaximumPacketSize()
+ */
+qint32 QPacketProtocol::maximumPacketSize() const
+{
+ return d->maxPacketSize;
+}
+
+/*!
+ Sets the maximum allowable packet size to \a max.
+
+ \sa QPacketProtocol::maximumPacketSize()
+ */
+qint32 QPacketProtocol::setMaximumPacketSize(qint32 max)
+{
+ if (max > (signed)sizeof(qint32))
+ d->maxPacketSize = max;
+ return d->maxPacketSize;
+}
+
+/*!
+ Returns a streamable object that is transmitted on destruction. For example
+
+ \code
+ protocol.send() << "Hello world" << 123;
+ \endcode
+
+ will send a packet containing "Hello world" and 123. To construct more
+ complex packets, explicitly construct a QPacket instance.
+ */
+QPacketAutoSend QPacketProtocol::send()
+{
+ return QPacketAutoSend(this);
+}
+
+/*!
+ \fn void QPacketProtocol::send(const QPacket & packet)
+
+ Transmit the \a packet.
+ */
+void QPacketProtocol::send(const QPacket & p)
+{
+ if (p.b.isEmpty())
+ return; // We don't send empty packets
+
+ qint64 sendSize = p.b.size() + sizeof(qint32);
+
+ d->sendingPackets.append(sendSize);
+ qint32 sendSize32 = sendSize;
+ qint64 writeBytes = d->dev->write((char *)&sendSize32, sizeof(qint32));
+ Q_ASSERT(writeBytes == sizeof(qint32));
+ writeBytes = d->dev->write(p.b);
+ Q_ASSERT(writeBytes == p.b.size());
+}
+
+/*!
+ Returns the number of received packets yet to be read.
+ */
+qint64 QPacketProtocol::packetsAvailable() const
+{
+ return d->packets.count();
+}
+
+/*!
+ Discard any unread packets.
+ */
+void QPacketProtocol::clear()
+{
+ d->packets.clear();
+}
+
+/*!
+ Return the next unread packet, or an invalid QPacket instance if no packets
+ are available. This method does NOT block.
+ */
+QPacket QPacketProtocol::read()
+{
+ if (0 == d->packets.count())
+ return QPacket();
+
+ QPacket rv(d->packets.at(0));
+ d->packets.removeFirst();
+ return rv;
+}
+
+/*
+ Returns the difference between msecs and elapsed. If msecs is -1,
+ however, -1 is returned.
+*/
+static int qt_timeout_value(int msecs, int elapsed)
+{
+ if (msecs == -1)
+ return -1;
+
+ int timeout = msecs - elapsed;
+ return timeout < 0 ? 0 : timeout;
+}
+
+/*!
+ This function locks until a new packet is available for reading and the
+ \l{QIODevice::}{readyRead()} signal has been emitted. The function
+ will timeout after \a msecs milliseconds; the default timeout is
+ 30000 milliseconds.
+
+ The function returns true if the readyRead() signal is emitted and
+ there is new data available for reading; otherwise it returns false
+ (if an error occurred or the operation timed out).
+ */
+
+bool QPacketProtocol::waitForReadyRead(int msecs)
+{
+ if (!d->packets.isEmpty())
+ return true;
+
+ QElapsedTimer stopWatch;
+ stopWatch.start();
+
+ d->waitingForPacket = true;
+ do {
+ if (!d->dev->waitForReadyRead(msecs))
+ return false;
+ if (!d->waitingForPacket)
+ return true;
+ msecs = qt_timeout_value(msecs, stopWatch.elapsed());
+ } while (true);
+}
+
+/*!
+ Return the QIODevice passed to the QPacketProtocol constructor.
+*/
+QIODevice *QPacketProtocol::device()
+{
+ return d->dev;
+}
+
+/*!
+ \fn void QPacketProtocol::readyRead()
+
+ Emitted whenever a new packet is received. Applications may use
+ QPacketProtocol::read() to retrieve this packet.
+ */
+
+/*!
+ \fn void QPacketProtocol::invalidPacket()
+
+ A packet larger than the maximum allowable packet size was received. The
+ packet will be discarded and, as it indicates corruption in the protocol, no
+ further packets will be received.
+ */
+
+/*!
+ \fn void QPacketProtocol::packetWritten()
+
+ Emitted each time a packet is completing written to the device. This signal
+ may be used for communications flow control.
+ */
+
+/*!
+ \class QPacket
+ \internal
+
+ \brief The QPacket class encapsulates an unfragmentable packet of data to be
+ transmitted by QPacketProtocol.
+
+ The QPacket class works together with QPacketProtocol to make it simple to
+ send arbitrary sized data "packets" across fragmented transports such as TCP
+ and UDP.
+
+ QPacket provides a QDataStream interface to an unfragmentable packet.
+ Applications should construct a QPacket, propagate it with data and then
+ transmit it over a QPacketProtocol instance. For example:
+ \code
+ QPacketProtocol protocol(...);
+
+ QPacket myPacket;
+ myPacket << "Hello world!" << 123;
+ protocol.send(myPacket);
+ \endcode
+
+ As long as both ends of the connection are using the QPacketProtocol class,
+ the data within this packet will be delivered unfragmented at the other end,
+ ready for extraction.
+
+ \code
+ QByteArray greeting;
+ int count;
+
+ QPacket myPacket = protocol.read();
+
+ myPacket >> greeting >> count;
+ \endcode
+
+ Only packets returned from QPacketProtocol::read() may be read from. QPacket
+ instances constructed by directly by applications are for transmission only
+ and are considered "write only". Attempting to read data from them will
+ result in undefined behavior.
+
+ \ingroup io
+ \sa QPacketProtocol
+ */
+
+/*!
+ Constructs an empty write-only packet.
+ */
+QPacket::QPacket()
+ : QDataStream(), buf(0)
+{
+ buf = new QBuffer(&b);
+ buf->open(QIODevice::WriteOnly);
+ setDevice(buf);
+ setVersion(QDataStream::Qt_4_7);
+}
+
+/*!
+ Destroys the QPacket instance.
+ */
+QPacket::~QPacket()
+{
+ if (buf) {
+ delete buf;
+ buf = 0;
+ }
+}
+
+/*!
+ Creates a copy of \a other. The initial stream positions are shared, but the
+ two packets are otherwise independent.
+ */
+QPacket::QPacket(const QPacket & other)
+ : QDataStream(), b(other.b), buf(0)
+{
+ buf = new QBuffer(&b);
+ buf->open(other.buf->openMode());
+ setDevice(buf);
+}
+
+/*!
+ \internal
+ */
+QPacket::QPacket(const QByteArray & ba)
+ : QDataStream(), b(ba), buf(0)
+{
+ buf = new QBuffer(&b);
+ buf->open(QIODevice::ReadOnly);
+ setDevice(buf);
+}
+
+/*!
+ Returns true if this packet is empty - that is, contains no data.
+ */
+bool QPacket::isEmpty() const
+{
+ return b.isEmpty();
+}
+
+/*!
+ Returns raw packet data.
+ */
+QByteArray QPacket::data() const
+{
+ return b;
+}
+
+/*!
+ Clears data in the packet. This is useful for reusing one writable packet.
+ For example
+ \code
+ QPacketProtocol protocol(...);
+
+ QPacket packet;
+
+ packet << "Hello world!" << 123;
+ protocol.send(packet);
+
+ packet.clear();
+ packet << "Goodbyte world!" << 789;
+ protocol.send(packet);
+ \endcode
+ */
+void QPacket::clear()
+{
+ QBuffer::OpenMode oldMode = buf->openMode();
+ buf->close();
+ b.clear();
+ buf->setBuffer(&b); // reset QBuffer internals with new size of b.
+ buf->open(oldMode);
+}
+
+/*!
+ \class QPacketAutoSend
+ \internal
+
+ \internal
+ */
+QPacketAutoSend::QPacketAutoSend(QPacketProtocol *_p)
+ : QPacket(), p(_p)
+{
+}
+
+QPacketAutoSend::~QPacketAutoSend()
+{
+ if (!b.isEmpty())
+ p->send(*this);
+}
+
+#include <qpacketprotocol.moc>
diff --git a/tools/qmlprofiler/qpacketprotocol.h b/tools/qmlprofiler/qpacketprotocol.h
new file mode 100644
index 0000000000..9c425b3988
--- /dev/null
+++ b/tools/qmlprofiler/qpacketprotocol.h
@@ -0,0 +1,117 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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, Digia gives you certain additional
+** rights. These rights are described in the Digia 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.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QPACKETPROTOCOL_H
+#define QPACKETPROTOCOL_H
+
+#include <QtCore/qobject.h>
+#include <QtCore/qdatastream.h>
+
+QT_BEGIN_NAMESPACE
+class QIODevice;
+class QBuffer;
+QT_END_NAMESPACE
+class QPacket;
+class QPacketAutoSend;
+class QPacketProtocolPrivate;
+
+class QPacketProtocol : public QObject
+{
+ Q_OBJECT
+public:
+ explicit QPacketProtocol(QIODevice *dev, QObject *parent = 0);
+ virtual ~QPacketProtocol();
+
+ qint32 maximumPacketSize() const;
+ qint32 setMaximumPacketSize(qint32);
+
+ QPacketAutoSend send();
+ void send(const QPacket &);
+
+ qint64 packetsAvailable() const;
+ QPacket read();
+
+ bool waitForReadyRead(int msecs = 3000);
+
+ void clear();
+
+ QIODevice *device();
+
+Q_SIGNALS:
+ void readyRead();
+ void invalidPacket();
+ void packetWritten();
+
+private:
+ QPacketProtocolPrivate *d;
+};
+
+
+class QPacket : public QDataStream
+{
+public:
+ QPacket();
+ QPacket(const QPacket &);
+ virtual ~QPacket();
+
+ void clear();
+ bool isEmpty() const;
+ QByteArray data() const;
+
+protected:
+ friend class QPacketProtocol;
+ QPacket(const QByteArray &ba);
+ QByteArray b;
+ QBuffer *buf;
+};
+
+class QPacketAutoSend : public QPacket
+{
+public:
+ virtual ~QPacketAutoSend();
+
+private:
+ friend class QPacketProtocol;
+ QPacketAutoSend(QPacketProtocol *);
+ QPacketProtocol *p;
+};
+
+#endif
diff --git a/tools/qmlprofiler/qqmldebugclient.cpp b/tools/qmlprofiler/qqmldebugclient.cpp
new file mode 100644
index 0000000000..bb57594a3b
--- /dev/null
+++ b/tools/qmlprofiler/qqmldebugclient.cpp
@@ -0,0 +1,409 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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, Digia gives you certain additional
+** rights. These rights are described in the Digia 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.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qqmldebugclient.h"
+#include "qpacketprotocol.h"
+
+#include <QtCore/qdebug.h>
+#include <QtCore/qstringlist.h>
+#include <QtNetwork/qnetworkproxy.h>
+
+const int protocolVersion = 1;
+const QString serverId = QLatin1String("QDeclarativeDebugServer");
+const QString clientId = QLatin1String("QDeclarativeDebugClient");
+
+class QQmlDebugClientPrivate
+{
+public:
+ QQmlDebugClientPrivate();
+
+ QString name;
+ QQmlDebugConnection *connection;
+};
+
+class QQmlDebugConnectionPrivate : public QObject
+{
+ Q_OBJECT
+public:
+ QQmlDebugConnectionPrivate(QQmlDebugConnection *c);
+ QQmlDebugConnection *q;
+ QPacketProtocol *protocol;
+ QIODevice *device;
+
+ bool gotHello;
+ QHash <QString, float> serverPlugins;
+ QHash<QString, QQmlDebugClient *> plugins;
+
+ void advertisePlugins();
+ void connectDeviceSignals();
+
+public Q_SLOTS:
+ void connected();
+ void readyRead();
+ void deviceAboutToClose();
+};
+
+QQmlDebugConnectionPrivate::QQmlDebugConnectionPrivate(QQmlDebugConnection *c)
+ : QObject(c), q(c), protocol(0), device(0), gotHello(false)
+{
+ protocol = new QPacketProtocol(q, this);
+ QObject::connect(c, SIGNAL(connected()), this, SLOT(connected()));
+ QObject::connect(protocol, SIGNAL(readyRead()), this, SLOT(readyRead()));
+}
+
+void QQmlDebugConnectionPrivate::advertisePlugins()
+{
+ if (!q->isConnected())
+ return;
+
+ QPacket pack;
+ pack << serverId << 1 << plugins.keys();
+ protocol->send(pack);
+ q->flush();
+}
+
+void QQmlDebugConnectionPrivate::connected()
+{
+ QPacket pack;
+ pack << serverId << 0 << protocolVersion << plugins.keys();
+ protocol->send(pack);
+ q->flush();
+}
+
+void QQmlDebugConnectionPrivate::readyRead()
+{
+ if (!gotHello) {
+ QPacket pack = protocol->read();
+ QString name;
+
+ pack >> name;
+
+ bool validHello = false;
+ if (name == clientId) {
+ int op = -1;
+ pack >> op;
+ if (op == 0) {
+ int version = -1;
+ pack >> version;
+ if (version == protocolVersion) {
+ QStringList pluginNames;
+ QList<float> pluginVersions;
+ pack >> pluginNames;
+ if (!pack.isEmpty())
+ pack >> pluginVersions;
+
+ const int pluginNamesSize = pluginNames.size();
+ const int pluginVersionsSize = pluginVersions.size();
+ for (int i = 0; i < pluginNamesSize; ++i) {
+ float pluginVersion = 1.0;
+ if (i < pluginVersionsSize)
+ pluginVersion = pluginVersions.at(i);
+ serverPlugins.insert(pluginNames.at(i), pluginVersion);
+ }
+
+ validHello = true;
+ }
+ }
+ }
+
+ if (!validHello) {
+ qWarning("QQmlDebugConnection: Invalid hello message");
+ QObject::disconnect(protocol, SIGNAL(readyRead()), this, SLOT(readyRead()));
+ return;
+ }
+ gotHello = true;
+
+ QHash<QString, QQmlDebugClient *>::Iterator iter = plugins.begin();
+ for (; iter != plugins.end(); ++iter) {
+ QQmlDebugClient::State newState = QQmlDebugClient::Unavailable;
+ if (serverPlugins.contains(iter.key()))
+ newState = QQmlDebugClient::Enabled;
+ iter.value()->stateChanged(newState);
+ }
+ }
+
+ while (protocol->packetsAvailable()) {
+ QPacket pack = protocol->read();
+ QString name;
+ pack >> name;
+
+ if (name == clientId) {
+ int op = -1;
+ pack >> op;
+
+ if (op == 1) {
+ // Service Discovery
+ QHash<QString, float> oldServerPlugins = serverPlugins;
+ serverPlugins.clear();
+
+ QStringList pluginNames;
+ QList<float> pluginVersions;
+ pack >> pluginNames;
+ if (!pack.isEmpty())
+ pack >> pluginVersions;
+
+ const int pluginNamesSize = pluginNames.size();
+ const int pluginVersionsSize = pluginVersions.size();
+ for (int i = 0; i < pluginNamesSize; ++i) {
+ float pluginVersion = 1.0;
+ if (i < pluginVersionsSize)
+ pluginVersion = pluginVersions.at(i);
+ serverPlugins.insert(pluginNames.at(i), pluginVersion);
+ }
+
+ QHash<QString, QQmlDebugClient *>::Iterator iter = plugins.begin();
+ for (; iter != plugins.end(); ++iter) {
+ const QString pluginName = iter.key();
+ QQmlDebugClient::State newSate = QQmlDebugClient::Unavailable;
+ if (serverPlugins.contains(pluginName))
+ newSate = QQmlDebugClient::Enabled;
+
+ if (oldServerPlugins.contains(pluginName)
+ != serverPlugins.contains(pluginName)) {
+ iter.value()->stateChanged(newSate);
+ }
+ }
+ } else {
+ qWarning() << "QQmlDebugConnection: Unknown control message id" << op;
+ }
+ } else {
+ QByteArray message;
+ pack >> message;
+
+ QHash<QString, QQmlDebugClient *>::Iterator iter =
+ plugins.find(name);
+ if (iter == plugins.end()) {
+ qWarning() << "QQmlDebugConnection: Message received for missing plugin" << name;
+ } else {
+ (*iter)->messageReceived(message);
+ }
+ }
+ }
+}
+
+void QQmlDebugConnectionPrivate::deviceAboutToClose()
+{
+ // This is nasty syntax but we want to emit our own aboutToClose signal (by calling QIODevice::close())
+ // without calling the underlying device close fn as that would cause an infinite loop
+ q->QIODevice::close();
+}
+
+QQmlDebugConnection::QQmlDebugConnection(QObject *parent)
+ : QIODevice(parent), d(new QQmlDebugConnectionPrivate(this))
+{
+}
+
+QQmlDebugConnection::~QQmlDebugConnection()
+{
+ QHash<QString, QQmlDebugClient*>::iterator iter = d->plugins.begin();
+ for (; iter != d->plugins.end(); ++iter) {
+ iter.value()->d->connection = 0;
+ iter.value()->stateChanged(QQmlDebugClient::NotConnected);
+ }
+}
+
+bool QQmlDebugConnection::isConnected() const
+{
+ return state() == QAbstractSocket::ConnectedState;
+}
+
+qint64 QQmlDebugConnection::readData(char *data, qint64 maxSize)
+{
+ return d->device->read(data, maxSize);
+}
+
+qint64 QQmlDebugConnection::writeData(const char *data, qint64 maxSize)
+{
+ return d->device->write(data, maxSize);
+}
+
+qint64 QQmlDebugConnection::bytesAvailable() const
+{
+ return d->device->bytesAvailable();
+}
+
+bool QQmlDebugConnection::isSequential() const
+{
+ return true;
+}
+
+void QQmlDebugConnection::close()
+{
+ if (isOpen()) {
+ QIODevice::close();
+ d->device->close();
+ emit stateChanged(QAbstractSocket::UnconnectedState);
+
+ QHash<QString, QQmlDebugClient*>::iterator iter = d->plugins.begin();
+ for (; iter != d->plugins.end(); ++iter) {
+ iter.value()->stateChanged(QQmlDebugClient::NotConnected);
+ }
+ }
+}
+
+bool QQmlDebugConnection::waitForConnected(int msecs)
+{
+ QAbstractSocket *socket = qobject_cast<QAbstractSocket*>(d->device);
+ if (socket)
+ return socket->waitForConnected(msecs);
+ return false;
+}
+
+QAbstractSocket::SocketState QQmlDebugConnection::state() const
+{
+ QAbstractSocket *socket = qobject_cast<QAbstractSocket*>(d->device);
+ if (socket)
+ return socket->state();
+
+ return QAbstractSocket::UnconnectedState;
+}
+
+void QQmlDebugConnection::flush()
+{
+ QAbstractSocket *socket = qobject_cast<QAbstractSocket*>(d->device);
+ if (socket) {
+ socket->flush();
+ return;
+ }
+}
+
+void QQmlDebugConnection::connectToHost(const QString &hostName, quint16 port)
+{
+ QTcpSocket *socket = new QTcpSocket(d);
+ socket->setProxy(QNetworkProxy::NoProxy);
+ d->device = socket;
+ d->connectDeviceSignals();
+ d->gotHello = false;
+ connect(socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SIGNAL(stateChanged(QAbstractSocket::SocketState)));
+ connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SIGNAL(error(QAbstractSocket::SocketError)));
+ connect(socket, SIGNAL(connected()), this, SIGNAL(connected()));
+ socket->connectToHost(hostName, port);
+ QIODevice::open(ReadWrite | Unbuffered);
+}
+
+void QQmlDebugConnectionPrivate::connectDeviceSignals()
+{
+ connect(device, SIGNAL(bytesWritten(qint64)), q, SIGNAL(bytesWritten(qint64)));
+ connect(device, SIGNAL(readyRead()), q, SIGNAL(readyRead()));
+ connect(device, SIGNAL(aboutToClose()), this, SLOT(deviceAboutToClose()));
+}
+
+//
+
+QQmlDebugClientPrivate::QQmlDebugClientPrivate()
+ : connection(0)
+{
+}
+
+QQmlDebugClient::QQmlDebugClient(const QString &name,
+ QQmlDebugConnection *parent)
+ : QObject(parent),
+ d(new QQmlDebugClientPrivate)
+{
+ d->name = name;
+ d->connection = parent;
+
+ if (!d->connection)
+ return;
+
+ if (d->connection->d->plugins.contains(name)) {
+ qWarning() << "QQmlDebugClient: Conflicting plugin name" << name;
+ d->connection = 0;
+ } else {
+ d->connection->d->plugins.insert(name, this);
+ d->connection->d->advertisePlugins();
+ }
+}
+
+QQmlDebugClient::~QQmlDebugClient()
+{
+ if (d->connection && d->connection->d) {
+ d->connection->d->plugins.remove(d->name);
+ d->connection->d->advertisePlugins();
+ }
+ delete d;
+}
+
+QString QQmlDebugClient::name() const
+{
+ return d->name;
+}
+
+float QQmlDebugClient::serviceVersion() const
+{
+ if (d->connection->d->serverPlugins.contains(d->name))
+ return d->connection->d->serverPlugins.value(d->name);
+ return -1;
+}
+
+QQmlDebugClient::State QQmlDebugClient::state() const
+{
+ if (!d->connection
+ || !d->connection->isConnected()
+ || !d->connection->d->gotHello)
+ return NotConnected;
+
+ if (d->connection->d->serverPlugins.contains(d->name))
+ return Enabled;
+
+ return Unavailable;
+}
+
+void QQmlDebugClient::sendMessage(const QByteArray &message)
+{
+ if (state() != Enabled)
+ return;
+
+ QPacket pack;
+ pack << d->name << message;
+ d->connection->d->protocol->send(pack);
+ d->connection->flush();
+}
+
+void QQmlDebugClient::stateChanged(State)
+{
+}
+
+void QQmlDebugClient::messageReceived(const QByteArray &)
+{
+}
+
+#include <qqmldebugclient.moc>
diff --git a/tools/qmlprofiler/qqmldebugclient.h b/tools/qmlprofiler/qqmldebugclient.h
new file mode 100644
index 0000000000..2aa9ad9706
--- /dev/null
+++ b/tools/qmlprofiler/qqmldebugclient.h
@@ -0,0 +1,109 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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, Digia gives you certain additional
+** rights. These rights are described in the Digia 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.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QQMLDEBUGCLIENT_H
+#define QQMLDEBUGCLIENT_H
+
+#include <QtNetwork/qtcpsocket.h>
+
+class QQmlDebugConnectionPrivate;
+class QQmlDebugConnection : public QIODevice
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(QQmlDebugConnection)
+public:
+ QQmlDebugConnection(QObject * = 0);
+ ~QQmlDebugConnection();
+
+ void connectToHost(const QString &hostName, quint16 port);
+
+ qint64 bytesAvailable() const;
+ bool isConnected() const;
+ QAbstractSocket::SocketState state() const;
+ void flush();
+ bool isSequential() const;
+ void close();
+ bool waitForConnected(int msecs = 30000);
+
+signals:
+ void connected();
+ void stateChanged(QAbstractSocket::SocketState socketState);
+ void error(QAbstractSocket::SocketError socketError);
+
+protected:
+ qint64 readData(char *data, qint64 maxSize);
+ qint64 writeData(const char *data, qint64 maxSize);
+
+private:
+ QQmlDebugConnectionPrivate *d;
+ friend class QQmlDebugClient;
+ friend class QQmlDebugClientPrivate;
+};
+
+class QQmlDebugClientPrivate;
+class QQmlDebugClient : public QObject
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(QQmlDebugClient)
+
+public:
+ enum State { NotConnected, Unavailable, Enabled };
+
+ QQmlDebugClient(const QString &, QQmlDebugConnection *parent);
+ ~QQmlDebugClient();
+
+ QString name() const;
+ float serviceVersion() const;
+ State state() const;
+
+ virtual void sendMessage(const QByteArray &);
+
+protected:
+ virtual void stateChanged(State);
+ virtual void messageReceived(const QByteArray &);
+
+private:
+ QQmlDebugClientPrivate *d;
+ friend class QQmlDebugConnection;
+ friend class QQmlDebugConnectionPrivate;
+};
+
+#endif // QQMLDEBUGCLIENT_H
diff --git a/tools/qmlscene/main.cpp b/tools/qmlscene/main.cpp
new file mode 100644
index 0000000000..afc4be875c
--- /dev/null
+++ b/tools/qmlscene/main.cpp
@@ -0,0 +1,545 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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, Digia gives you certain additional
+** rights. These rights are described in the Digia 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.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtCore/qdebug.h>
+#include <QtCore/qabstractanimation.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qmath.h>
+#include <QtCore/qdatetime.h>
+
+#include <QtGui/QGuiApplication>
+
+#include <QtQml/qqml.h>
+#include <QtQml/qqmlengine.h>
+#include <QtQml/qqmlcomponent.h>
+#include <QtQml/qqmlcontext.h>
+
+#include <QtQuick/qquickitem.h>
+#include <QtQuick/qquickview.h>
+
+#include <private/qabstractanimation_p.h>
+
+#ifdef QT_WIDGETS_LIB
+#include <QtWidgets/QApplication>
+#include <QtWidgets/QFileDialog>
+#endif
+
+#include <QtCore/QTranslator>
+#include <QtCore/QLibraryInfo>
+
+#ifdef QML_RUNTIME_TESTING
+class RenderStatistics
+{
+public:
+ static void updateStats();
+ static void printTotalStats();
+private:
+ static QVector<qreal> timePerFrame;
+ static QVector<int> timesPerFrames;
+};
+
+QVector<qreal> RenderStatistics::timePerFrame;
+QVector<int> RenderStatistics::timesPerFrames;
+
+void RenderStatistics::updateStats()
+{
+ static QTime time;
+ static int frames;
+ static int lastTime;
+
+ if (frames == 0) {
+ time.start();
+ } else {
+ int elapsed = time.elapsed();
+ timesPerFrames.append(elapsed - lastTime);
+ lastTime = elapsed;
+
+ if (elapsed > 5000) {
+ qreal avgtime = elapsed / (qreal) frames;
+ qreal var = 0;
+ for (int i = 0; i < timesPerFrames.size(); ++i) {
+ qreal diff = timesPerFrames.at(i) - avgtime;
+ var += diff * diff;
+ }
+ var /= timesPerFrames.size();
+
+ qDebug("Average time per frame: %f ms (%i fps), std.dev: %f ms", avgtime, qRound(1000. / avgtime), qSqrt(var));
+
+ timePerFrame.append(avgtime);
+ timesPerFrames.clear();
+ time.start();
+ lastTime = 0;
+ frames = 0;
+ }
+ }
+ ++frames;
+}
+
+void RenderStatistics::printTotalStats()
+{
+ int count = timePerFrame.count();
+ if (count == 0)
+ return;
+
+ qreal minTime = 0;
+ qreal maxTime = 0;
+ qreal avg = 0;
+ for (int i = 0; i < count; ++i) {
+ minTime = minTime == 0 ? timePerFrame.at(i) : qMin(minTime, timePerFrame.at(i));
+ maxTime = qMax(maxTime, timePerFrame.at(i));
+ avg += timePerFrame.at(i);
+ }
+ avg /= count;
+
+ qDebug(" ");
+ qDebug("----- Statistics -----");
+ qDebug("Average time per frame: %f ms (%i fps)", avg, qRound(1000. / avg));
+ qDebug("Best time per frame: %f ms (%i fps)", minTime, int(1000 / minTime));
+ qDebug("Worst time per frame: %f ms (%i fps)", maxTime, int(1000 / maxTime));
+ qDebug("----------------------");
+ qDebug(" ");
+}
+#endif
+
+struct Options
+{
+ Options()
+ : originalQml(false)
+ , originalQmlRaster(false)
+ , maximized(false)
+ , fullscreen(false)
+ , transparent(false)
+ , clip(false)
+ , versionDetection(true)
+ , slowAnimations(false)
+ , quitImmediately(false)
+ , resizeViewToRootItem(false)
+ , multisample(false)
+ {
+ }
+
+ QUrl file;
+ bool originalQml;
+ bool originalQmlRaster;
+ bool maximized;
+ bool fullscreen;
+ bool transparent;
+ bool scenegraphOnGraphicsview;
+ bool clip;
+ bool versionDetection;
+ bool slowAnimations;
+ bool quitImmediately;
+ bool resizeViewToRootItem;
+ bool multisample;
+ QString translationFile;
+};
+
+#if defined(QMLSCENE_BUNDLE)
+QFileInfoList findQmlFiles(const QString &dirName)
+{
+ QDir dir(dirName);
+
+ QFileInfoList ret;
+ if (dir.exists()) {
+ QFileInfoList fileInfos = dir.entryInfoList(QStringList() << "*.qml",
+ QDir::Files | QDir::AllDirs | QDir::NoDotAndDotDot);
+
+ foreach (QFileInfo fileInfo, fileInfos) {
+ if (fileInfo.isDir())
+ ret += findQmlFiles(fileInfo.filePath());
+ else if (fileInfo.fileName().length() > 0 && fileInfo.fileName().at(0).isLower())
+ ret.append(fileInfo);
+ }
+ }
+
+ return ret;
+}
+
+static int displayOptionsDialog(Options *options)
+{
+ QDialog dialog;
+
+ QFormLayout *layout = new QFormLayout(&dialog);
+
+ QComboBox *qmlFileComboBox = new QComboBox(&dialog);
+ QFileInfoList fileInfos = findQmlFiles(":/bundle") + findQmlFiles("./qmlscene-resources");
+
+ foreach (QFileInfo fileInfo, fileInfos)
+ qmlFileComboBox->addItem(fileInfo.dir().dirName() + "/" + fileInfo.fileName(), QVariant::fromValue(fileInfo));
+
+ QCheckBox *originalCheckBox = new QCheckBox(&dialog);
+ originalCheckBox->setText("Use original QML viewer");
+ originalCheckBox->setChecked(options->originalQml);
+
+ QCheckBox *fullscreenCheckBox = new QCheckBox(&dialog);
+ fullscreenCheckBox->setText("Start fullscreen");
+ fullscreenCheckBox->setChecked(options->fullscreen);
+
+ QCheckBox *maximizedCheckBox = new QCheckBox(&dialog);
+ maximizedCheckBox->setText("Start maximized");
+ maximizedCheckBox->setChecked(options->maximized);
+
+ QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel,
+ Qt::Horizontal,
+ &dialog);
+ QObject::connect(buttonBox, SIGNAL(accepted()), &dialog, SLOT(accept()));
+ QObject::connect(buttonBox, SIGNAL(rejected()), &dialog, SLOT(reject()));
+
+ layout->addRow("Qml file:", qmlFileComboBox);
+ layout->addWidget(originalCheckBox);
+ layout->addWidget(maximizedCheckBox);
+ layout->addWidget(fullscreenCheckBox);
+ layout->addWidget(buttonBox);
+
+ int result = dialog.exec();
+ if (result == QDialog::Accepted) {
+ QVariant variant = qmlFileComboBox->itemData(qmlFileComboBox->currentIndex());
+ QFileInfo fileInfo = variant.value<QFileInfo>();
+
+ if (fileInfo.canonicalFilePath().startsWith(":"))
+ options->file = QUrl("qrc" + fileInfo.canonicalFilePath());
+ else
+ options->file = QUrl::fromLocalFile(fileInfo.canonicalFilePath());
+ options->originalQml = originalCheckBox->isChecked();
+ options->maximized = maximizedCheckBox->isChecked();
+ options->fullscreen = fullscreenCheckBox->isChecked();
+ }
+ return result;
+}
+#endif
+
+static bool checkVersion(const QUrl &url)
+{
+ if (!qgetenv("QMLSCENE_IMPORT_NAME").isEmpty())
+ qWarning("QMLSCENE_IMPORT_NAME is no longer supported.");
+
+ QString fileName = url.toLocalFile();
+ if (fileName.isEmpty()) {
+ qWarning("qmlscene: filename required.");
+ return false;
+ }
+
+ QFile f(fileName);
+ if (!f.open(QFile::ReadOnly | QFile::Text)) {
+ qWarning("qmlscene: failed to check version of file '%s', could not open...",
+ qPrintable(fileName));
+ return false;
+ }
+
+ QRegExp quick1("^\\s*import +QtQuick +1\\.\\w*");
+ QRegExp qt47("^\\s*import +Qt +4\\.7");
+
+ QTextStream stream(&f);
+ bool codeFound= false;
+ while (!codeFound) {
+ QString line = stream.readLine();
+ if (line.contains("{")) {
+ codeFound = true;
+ } else {
+ QString import;
+ if (quick1.indexIn(line) >= 0)
+ import = quick1.cap(0).trimmed();
+ else if (qt47.indexIn(line) >= 0)
+ import = qt47.cap(0).trimmed();
+
+ if (!import.isNull()) {
+ qWarning("qmlscene: '%s' is no longer supported.\n"
+ "Use qmlviewer to load file '%s'.",
+ qPrintable(import),
+ qPrintable(fileName));
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+static void displayFileDialog(Options *options)
+{
+#if defined(QT_WIDGETS_LIB) && !defined(QT_NO_FILEDIALOG)
+ QString fileName = QFileDialog::getOpenFileName(0, "Open QML file", QString(), "QML Files (*.qml)");
+ if (!fileName.isEmpty()) {
+ QFileInfo fi(fileName);
+ options->file = QUrl::fromLocalFile(fi.canonicalFilePath());
+ }
+#else
+ Q_UNUSED(options);
+ qWarning("No filename specified...");
+#endif
+}
+
+#ifndef QT_NO_TRANSLATION
+static void loadTranslationFile(QTranslator &translator, const QString& directory)
+{
+ translator.load(QLatin1String("qml_" )+QLocale::system().name(), directory + QLatin1String("/i18n"));
+ QCoreApplication::installTranslator(&translator);
+}
+#endif
+
+static void loadDummyDataFiles(QQmlEngine &engine, const QString& directory)
+{
+ QDir dir(directory+"/dummydata", "*.qml");
+ QStringList list = dir.entryList();
+ for (int i = 0; i < list.size(); ++i) {
+ QString qml = list.at(i);
+ QFile f(dir.filePath(qml));
+ f.open(QIODevice::ReadOnly);
+ QByteArray data = f.readAll();
+ QQmlComponent comp(&engine);
+ comp.setData(data, QUrl());
+ QObject *dummyData = comp.create();
+
+ if(comp.isError()) {
+ QList<QQmlError> errors = comp.errors();
+ foreach (const QQmlError &error, errors)
+ qWarning() << error;
+ }
+
+ if (dummyData) {
+ qWarning() << "Loaded dummy data:" << dir.filePath(qml);
+ qml.truncate(qml.length()-4);
+ engine.rootContext()->setContextProperty(qml, dummyData);
+ dummyData->setParent(&engine);
+ }
+ }
+}
+
+static void usage()
+{
+ qWarning("Usage: qmlscene [options] <filename>");
+ qWarning(" ");
+ qWarning(" Options:");
+ qWarning(" --maximized ............................... Run maximized");
+ qWarning(" --fullscreen .............................. Run fullscreen");
+ qWarning(" --transparent ............................. Make the window transparent");
+ qWarning(" --multisample ............................. Enable multisampling (OpenGL anti-aliasing)");
+ qWarning(" --no-version-detection .................... Do not try to detect the version of the .qml file");
+ qWarning(" --slow-animations ......................... Run all animations in slow motion");
+ qWarning(" --resize-to-root .......................... Resize the window to the size of the root item");
+ qWarning(" --quit .................................... Quit immediately after starting");
+ qWarning(" -I <path> ................................. Add <path> to the list of import paths");
+ qWarning(" -B <name> <file> .......................... Add a named bundle");
+ qWarning(" -translation <translationfile> ............ Set the language to run in");
+
+ qWarning(" ");
+ exit(1);
+}
+
+int main(int argc, char ** argv)
+{
+ Options options;
+
+ QStringList imports;
+ QList<QPair<QString, QString> > bundles;
+ for (int i = 1; i < argc; ++i) {
+ if (*argv[i] != '-' && QFileInfo(QFile::decodeName(argv[i])).exists()) {
+ options.file = QUrl::fromLocalFile(argv[i]);
+ } else {
+ const QString lowerArgument = QString::fromLatin1(argv[i]).toLower();
+ if (lowerArgument == QLatin1String("--maximized"))
+ options.maximized = true;
+ else if (lowerArgument == QLatin1String("--fullscreen"))
+ options.fullscreen = true;
+ else if (lowerArgument == QLatin1String("--transparent"))
+ options.transparent = true;
+ else if (lowerArgument == QLatin1String("--clip"))
+ options.clip = true;
+ else if (lowerArgument == QLatin1String("--no-version-detection"))
+ options.versionDetection = false;
+ else if (lowerArgument == QLatin1String("--slow-animations"))
+ options.slowAnimations = true;
+ else if (lowerArgument == QLatin1String("--quit"))
+ options.quitImmediately = true;
+ else if (lowerArgument == QLatin1String("-translation"))
+ options.translationFile = QLatin1String(argv[++i]);
+ else if (lowerArgument == QLatin1String("--resize-to-root"))
+ options.resizeViewToRootItem = true;
+ else if (lowerArgument == QLatin1String("--multisample"))
+ options.multisample = true;
+ else if (lowerArgument == QLatin1String("-i") && i + 1 < argc)
+ imports.append(QString::fromLatin1(argv[++i]));
+ else if (lowerArgument == QLatin1String("-b") && i + 2 < argc) {
+ QString name = QString::fromLatin1(argv[++i]);
+ QString file = QString::fromLatin1(argv[++i]);
+ bundles.append(qMakePair(name, file));
+ } else if (lowerArgument == QLatin1String("--help")
+ || lowerArgument == QLatin1String("-help")
+ || lowerArgument == QLatin1String("--h")
+ || lowerArgument == QLatin1String("-h"))
+ usage();
+ }
+ }
+
+#ifdef QT_WIDGETS_LIB
+ QApplication app(argc, argv);
+#else
+ QGuiApplication app(argc, argv);
+#endif
+ app.setApplicationName("QtQmlViewer");
+ app.setOrganizationName("Qt Project");
+ app.setOrganizationDomain("qt-project.org");
+
+#ifndef QT_NO_TRANSLATION
+ QTranslator translator;
+ QTranslator qtTranslator;
+ QString sysLocale = QLocale::system().name();
+ if (translator.load(QLatin1String("qmlscene_") + sysLocale, QLibraryInfo::location(QLibraryInfo::TranslationsPath))) {
+ app.installTranslator(&translator);
+ if (qtTranslator.load(QLatin1String("qt_") + sysLocale, QLibraryInfo::location(QLibraryInfo::TranslationsPath))) {
+ app.installTranslator(&qtTranslator);
+ } else {
+ app.removeTranslator(&translator);
+ }
+ }
+
+ QTranslator qmlTranslator;
+ if (!options.translationFile.isEmpty()) {
+ if (qmlTranslator.load(options.translationFile)) {
+ app.installTranslator(&qmlTranslator);
+ } else {
+ qWarning() << "Could not load the translation file" << options.translationFile;
+ }
+ }
+#endif
+
+ QUnifiedTimer::instance()->setSlowModeEnabled(options.slowAnimations);
+
+ if (options.file.isEmpty())
+#if defined(QMLSCENE_BUNDLE)
+ displayOptionsDialog(&options);
+#else
+ displayFileDialog(&options);
+#endif
+
+ int exitCode = 0;
+
+ if (!options.file.isEmpty()) {
+ if (!options.versionDetection || checkVersion(options.file)) {
+#ifndef QT_NO_TRANSLATION
+ QTranslator translator;
+#endif
+
+ // TODO: as soon as the engine construction completes, the debug service is
+ // listening for connections. But actually we aren't ready to debug anything.
+ QQmlEngine engine;
+ QQmlComponent *component = new QQmlComponent(&engine);
+ for (int i = 0; i < imports.size(); ++i)
+ engine.addImportPath(imports.at(i));
+ for (int i = 0; i < bundles.size(); ++i)
+ engine.addNamedBundle(bundles.at(i).first, bundles.at(i).second);
+ if (options.file.isLocalFile()) {
+ QFileInfo fi(options.file.toLocalFile());
+#ifndef QT_NO_TRANSLATION
+ loadTranslationFile(translator, fi.path());
+#endif
+ loadDummyDataFiles(engine, fi.path());
+ }
+ QObject::connect(&engine, SIGNAL(quit()), QCoreApplication::instance(), SLOT(quit()));
+ component->loadUrl(options.file);
+ if ( !component->isReady() ) {
+ qWarning("%s", qPrintable(component->errorString()));
+ return -1;
+ }
+
+ QObject *topLevel = component->create();
+ QQuickWindow *window = qobject_cast<QQuickWindow *>(topLevel);
+ QQuickView* qxView = 0;
+ if (!window) {
+ QQuickItem *contentItem = qobject_cast<QQuickItem *>(topLevel);
+ if (contentItem) {
+ qxView = new QQuickView(&engine, NULL);
+ window = qxView;
+ // Set window default properties; the qml can still override them
+ QString oname = contentItem->objectName();
+ window->setTitle(oname.isEmpty() ? QString::fromLatin1("qmlscene") : QString::fromLatin1("qmlscene: ") + oname);
+ window->setFlags(Qt::Window | Qt::WindowSystemMenuHint | Qt::WindowTitleHint | Qt::WindowMinMaxButtonsHint | Qt::WindowCloseButtonHint | Qt::WindowFullscreenButtonHint);
+ if (options.resizeViewToRootItem)
+ qxView->setResizeMode(QQuickView::SizeViewToRootObject);
+ else
+ qxView->setResizeMode(QQuickView::SizeRootObjectToView);
+ qxView->setContent(options.file, component, contentItem);
+ }
+ }
+
+ if (window) {
+ QSurfaceFormat surfaceFormat = window->requestedFormat();
+ if (options.multisample)
+ surfaceFormat.setSamples(16);
+ if (options.transparent) {
+ surfaceFormat.setAlphaBufferSize(8);
+ window->setClearBeforeRendering(true);
+ window->setColor(QColor(Qt::transparent));
+ window->setFlags(Qt::FramelessWindowHint);
+ }
+ window->setFormat(surfaceFormat);
+
+ if (options.fullscreen)
+ window->showFullScreen();
+ else if (options.maximized)
+ window->showMaximized();
+ else
+ window->show();
+ }
+
+ if (options.quitImmediately)
+ QMetaObject::invokeMethod(QCoreApplication::instance(), "quit", Qt::QueuedConnection);
+
+ // Now would be a good time to inform the debug service to start listening.
+
+ exitCode = app.exec();
+
+#ifdef QML_RUNTIME_TESTING
+ RenderStatistics::printTotalStats();
+#endif
+ // Ready to exit. If we created qxView, it owns the component;
+ // otherwise, the ownership is still right here. Nobody deletes the engine
+ // (which is odd since the container constructor takes the engine pointer),
+ // but it's stack-allocated anyway.
+ if (qxView)
+ delete qxView;
+ else
+ delete component;
+ }
+ }
+
+ return exitCode;
+}
diff --git a/tools/qmlscene/qmlscene.pro b/tools/qmlscene/qmlscene.pro
new file mode 100644
index 0000000000..719110ad71
--- /dev/null
+++ b/tools/qmlscene/qmlscene.pro
@@ -0,0 +1,8 @@
+QT += qml quick core-private
+qtHaveModule(widgets): QT += widgets
+
+SOURCES += main.cpp
+
+DEFINES += QML_RUNTIME_TESTING QT_QML_DEBUG_NO_WARNING
+
+load(qt_tool)
diff --git a/tools/qmltestrunner/main.cpp b/tools/qmltestrunner/main.cpp
new file mode 100644
index 0000000000..3d6407f948
--- /dev/null
+++ b/tools/qmltestrunner/main.cpp
@@ -0,0 +1,51 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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, Digia gives you certain additional
+** rights. These rights are described in the Digia 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.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtQuickTest/quicktest.h>
+#include <QtCore/qstring.h>
+#ifdef QT_OPENGL_LIB
+#include <QtOpenGL/qgl.h>
+#endif
+
+int main(int argc, char **argv)
+{
+ return quick_test_main(argc, argv, "qmltestrunner", ".");
+}
diff --git a/tools/qmltestrunner/qmltestrunner.pro b/tools/qmltestrunner/qmltestrunner.pro
new file mode 100644
index 0000000000..5184c1f1a4
--- /dev/null
+++ b/tools/qmltestrunner/qmltestrunner.pro
@@ -0,0 +1,5 @@
+SOURCES += main.cpp
+
+QT += qml qmltest
+
+load(qt_tool)
diff --git a/tools/tools.pro b/tools/tools.pro
index f783b654b5..86e2f3804f 100644
--- a/tools/tools.pro
+++ b/tools/tools.pro
@@ -1,2 +1,8 @@
TEMPLATE = subdirs
-SUBDIRS += v4
+qtHaveModule(quick): SUBDIRS += qmlscene qmlplugindump
+qtHaveModule(qmltest): SUBDIRS += qmltestrunner
+SUBDIRS += \
+ qmlmin \
+ qmlprofiler \
+ qmlbundle
+qtHaveModule(quick):qtHaveModule(widgets): SUBDIRS += qmleasing