diff options
Diffstat (limited to 'tools')
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 |