diff options
Diffstat (limited to 'src/plugins')
33 files changed, 2251 insertions, 109 deletions
diff --git a/src/plugins/qmltooling/packetprotocol/qpacketprotocol.cpp b/src/plugins/qmltooling/packetprotocol/qpacketprotocol.cpp index e1d6263e36..c256501301 100644 --- a/src/plugins/qmltooling/packetprotocol/qpacketprotocol.cpp +++ b/src/plugins/qmltooling/packetprotocol/qpacketprotocol.cpp @@ -106,6 +106,9 @@ class QPacketProtocolPrivate : public QObjectPrivate public: QPacketProtocolPrivate(QIODevice *dev); + bool writeToDevice(const char *bytes, qint64 size); + bool readFromDevice(char *buffer, qint64 size); + QList<qint32> sendingPackets; QList<QByteArray> packets; QByteArray inProgress; @@ -143,18 +146,18 @@ void QPacketProtocol::send(const QByteArray &data) return; // We don't send empty packets if (data.size() > maxSize) { - emit invalidPacket(); + emit error(); return; } - qint32 sendSize = data.size() + sizeof(qint32); + const qint32 sendSize = data.size() + static_cast<qint32>(sizeof(qint32)); d->sendingPackets.append(sendSize); + qint32 sendSizeLE = qToLittleEndian(sendSize); - qint64 writeBytes = d->dev->write((char *)&sendSizeLE, sizeof(qint32)); - Q_UNUSED(writeBytes); - Q_ASSERT(writeBytes == sizeof(qint32)); - writeBytes = d->dev->write(data); - Q_ASSERT(writeBytes == data.size()); + if (!d->writeToDevice((const char *)&sendSizeLE, sizeof(qint32)) + || !d->writeToDevice(data.data(), data.size())) { + emit error(); + } } /*! @@ -240,28 +243,41 @@ void QPacketProtocol::readyToRead() // Need to get trailing data if (-1 == d->inProgressSize) { // We need a size header of sizeof(qint32) - if (sizeof(qint32) > (uint)d->dev->bytesAvailable()) + if (static_cast<qint64>(sizeof(qint32)) > d->dev->bytesAvailable()) return; // Read size header qint32 inProgressSizeLE; - const qint64 read = d->dev->read((char *)&inProgressSizeLE, sizeof(qint32)); + if (!d->readFromDevice((char *)&inProgressSizeLE, sizeof(qint32))) { + emit error(); + return; + } d->inProgressSize = qFromLittleEndian(inProgressSizeLE); // Check sizing constraints - if (read != sizeof(qint32) || d->inProgressSize < read) { + if (d->inProgressSize < qint32(sizeof(qint32))) { disconnect(d->dev, &QIODevice::readyRead, this, &QPacketProtocol::readyToRead); disconnect(d->dev, &QIODevice::aboutToClose, this, &QPacketProtocol::aboutToClose); disconnect(d->dev, &QIODevice::bytesWritten, this, &QPacketProtocol::bytesWritten); d->dev = nullptr; - emit invalidPacket(); + emit error(); return; } - d->inProgressSize -= read; + d->inProgressSize -= sizeof(qint32); } else { - d->inProgress.append(d->dev->read(d->inProgressSize - d->inProgress.size())); + const int bytesToRead = static_cast<int>( + qMin(d->dev->bytesAvailable(), + static_cast<qint64>(d->inProgressSize - d->inProgress.size()))); + + QByteArray toRead(bytesToRead, Qt::Uninitialized); + if (!d->readFromDevice(toRead.data(), toRead.length())) { + emit error(); + return; + } + + d->inProgress.append(toRead); if (d->inProgressSize == d->inProgress.size()) { // Packet has arrived! d->packets.append(d->inProgress); @@ -281,6 +297,30 @@ QPacketProtocolPrivate::QPacketProtocolPrivate(QIODevice *dev) : { } +bool QPacketProtocolPrivate::writeToDevice(const char *bytes, qint64 size) +{ + qint64 totalWritten = 0; + while (totalWritten < size) { + const qint64 chunkSize = dev->write(bytes + totalWritten, size - totalWritten); + if (chunkSize < 0) + return false; + totalWritten += chunkSize; + } + return totalWritten == size; +} + +bool QPacketProtocolPrivate::readFromDevice(char *buffer, qint64 size) +{ + qint64 totalRead = 0; + while (totalRead < size) { + const qint64 chunkSize = dev->read(buffer + totalRead, size - totalRead); + if (chunkSize < 0) + return false; + totalRead += chunkSize; + } + return totalRead == size; +} + /*! \fn void QPacketProtocol::readyRead() diff --git a/src/plugins/qmltooling/packetprotocol/qpacketprotocol_p.h b/src/plugins/qmltooling/packetprotocol/qpacketprotocol_p.h index 35edb568aa..a478fc9996 100644 --- a/src/plugins/qmltooling/packetprotocol/qpacketprotocol_p.h +++ b/src/plugins/qmltooling/packetprotocol/qpacketprotocol_p.h @@ -72,7 +72,7 @@ public: Q_SIGNALS: void readyRead(); - void invalidPacket(); + void error(); private: void aboutToClose(); diff --git a/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.cpp b/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.cpp index 95e6d5704c..a437b7ccc7 100644 --- a/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.cpp +++ b/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.cpp @@ -46,6 +46,7 @@ #include <private/qv4objectiterator_p.h> #include <private/qv4identifier_p.h> #include <private/qv4runtime_p.h> +#include <private/qv4identifiertable_p.h> #include <private/qqmlcontext_p.h> #include <private/qqmlengine_p.h> @@ -96,18 +97,17 @@ int QV4DataCollector::encodeScopeType(QV4::Heap::ExecutionContext::ContextType s { switch (scopeType) { case QV4::Heap::ExecutionContext::Type_GlobalContext: - return 0; - case QV4::Heap::ExecutionContext::Type_CatchContext: - return 4; + break; case QV4::Heap::ExecutionContext::Type_WithContext: return 2; case QV4::Heap::ExecutionContext::Type_CallContext: return 1; case QV4::Heap::ExecutionContext::Type_QmlContext: return 3; - default: - return -1; + case QV4::Heap::ExecutionContext::Type_BlockContext: + return 4; } + return 0; } QV4DataCollector::QV4DataCollector(QV4::ExecutionEngine *engine) @@ -268,9 +268,9 @@ bool QV4DataCollector::collectScope(QJsonObject *dict, int frameNr, int scopeNr) Refs collectedRefs; QV4::ScopedValue v(scope); - QV4::InternalClass *ic = ctxt->internalClass(); + QV4::Heap::InternalClass *ic = ctxt->internalClass(); for (uint i = 0; i < ic->size; ++i) { - QString name = ic->nameMap[i]->string; + QString name = ic->nameMap[i].toQString(); names.append(name); v = static_cast<QV4::Heap::CallContext *>(ctxt->d())->locals[i]; collectedRefs.append(collect(v)); @@ -394,18 +394,18 @@ QV4DataCollector::Ref QV4DataCollector::addRef(QV4::Value value, bool deduplicat { std::swap(*hasExceptionLoc, hadException); } }; - // if we wouldn't do this, the putIndexed won't work. + // if we wouldn't do this, the put won't work. ExceptionStateSaver resetExceptionState(engine()); QV4::Scope scope(engine()); QV4::ScopedObject array(scope, m_values.value()); if (deduplicate) { for (Ref i = 0; i < array->getLength(); ++i) { - if (array->getIndexed(i) == value.rawValue() && !m_specialRefs.contains(i)) + if (array->get(i) == value.rawValue() && !m_specialRefs.contains(i)) return i; } } Ref ref = array->getLength(); - array->putIndexed(ref, value); + array->put(ref, value); Q_ASSERT(array->getLength() - 1 == ref); return ref; } @@ -415,7 +415,7 @@ QV4::ReturnedValue QV4DataCollector::getValue(Ref ref) QV4::Scope scope(engine()); QV4::ScopedObject array(scope, m_values.value()); Q_ASSERT(ref < array->getLength()); - return array->getIndexed(ref, nullptr); + return array->get(ref, nullptr); } // TODO: Drop this method once we don't need to support namesAsObjects anymore diff --git a/src/plugins/qmltooling/qmldbg_debugger/qv4debugjob.cpp b/src/plugins/qmltooling/qmldbg_debugger/qv4debugjob.cpp index 7950d21612..70f71de6ca 100644 --- a/src/plugins/qmltooling/qmldbg_debugger/qv4debugjob.cpp +++ b/src/plugins/qmltooling/qmldbg_debugger/qv4debugjob.cpp @@ -65,7 +65,7 @@ void JavaScriptJob::run() QV4::Scope scope(engine); QV4::ScopedContext ctx(scope, engine->currentStackFrame ? engine->currentContext() - : engine->rootContext()); + : engine->scriptContext()); QObject scopeObject; QV4::CppStackFrame *frame = engine->currentStackFrame; @@ -103,7 +103,7 @@ void JavaScriptJob::run() } } - QV4::Script script(ctx, QV4::Compiler::EvalCode, this->script); + QV4::Script script(ctx, QV4::Compiler::ContextType::Eval, this->script); if (const QV4::Function *function = frame ? frame->v4Function : engine->globalCode) script.strictMode = function->isStrict(); diff --git a/src/plugins/qmltooling/qmldbg_nativedebugger/qqmlnativedebugservice.cpp b/src/plugins/qmltooling/qmldbg_nativedebugger/qqmlnativedebugservice.cpp index b19115aa60..b98cfffb6e 100644 --- a/src/plugins/qmltooling/qmldbg_nativedebugger/qqmlnativedebugservice.cpp +++ b/src/plugins/qmltooling/qmldbg_nativedebugger/qqmlnativedebugservice.cpp @@ -51,6 +51,7 @@ #include <private/qv4runtime_p.h> #include <private/qversionedpacket_p.h> #include <private/qqmldebugserviceinterfaces_p.h> +#include <private/qv4identifiertable_p.h> #include <QtQml/qjsengine.h> #include <QtCore/qjsonarray.h> @@ -252,9 +253,9 @@ QV4::ReturnedValue NativeDebugger::evaluateExpression(const QString &expression) m_runningJob = true; QV4::ExecutionContext *ctx = m_engine->currentStackFrame ? m_engine->currentContext() - : m_engine->rootContext(); + : m_engine->scriptContext(); - QV4::Script script(ctx, QV4::Compiler::EvalCode, expression); + QV4::Script script(ctx, QV4::Compiler::ContextType::Eval, expression); if (const QV4::Function *function = m_engine->currentStackFrame ? m_engine->currentStackFrame->v4Function : m_engine->globalCode) script.strictMode = function->isStrict(); @@ -414,7 +415,7 @@ void Collector::collect(QJsonArray *out, const QString &parentIName, const QStri if (isExpanded(iname)) { QJsonArray children; for (uint i = 0; i < n; ++i) { - QV4::ReturnedValue v = array->getIndexed(i); + QV4::ReturnedValue v = array->get(i); QV4::ScopedValue sval(scope, v); collect(&children, iname, QString::number(i), *sval); } @@ -493,10 +494,10 @@ void NativeDebugger::handleVariables(QJsonObject *response, const QJsonObject &a collector.collect(&output, QString(), QStringLiteral("this"), thisObject); QV4::Scoped<QV4::CallContext> callContext(scope, frame->callContext()); if (callContext) { - QV4::InternalClass *ic = callContext->internalClass(); + QV4::Heap::InternalClass *ic = callContext->internalClass(); QV4::ScopedValue v(scope); for (uint i = 0; i < ic->size; ++i) { - QString name = ic->nameMap[i]->string; + QString name = ic->nameMap[i].toQString(); v = callContext->d()->locals[i]; collector.collect(&output, QString(), name, v); } diff --git a/src/plugins/qmltooling/qmldbg_preview/qmldbg_preview.pro b/src/plugins/qmltooling/qmldbg_preview/qmldbg_preview.pro new file mode 100644 index 0000000000..08686a43e3 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_preview/qmldbg_preview.pro @@ -0,0 +1,29 @@ +QT += core-private qml-private packetprotocol-private network quick-private gui-private + +TARGET = qmldbg_preview + +SOURCES += \ + $$PWD/qqmlpreviewblacklist.cpp \ + $$PWD/qqmlpreviewfileengine.cpp \ + $$PWD/qqmlpreviewfileloader.cpp \ + $$PWD/qqmlpreviewhandler.cpp \ + $$PWD/qqmlpreviewposition.cpp \ + $$PWD/qqmlpreviewservice.cpp \ + $$PWD/qqmlpreviewservicefactory.cpp + +HEADERS += \ + $$PWD/qqmlpreviewblacklist.h \ + $$PWD/qqmlpreviewfileengine.h \ + $$PWD/qqmlpreviewfileloader.h \ + $$PWD/qqmlpreviewhandler.h \ + $$PWD/qqmlpreviewposition.h \ + $$PWD/qqmlpreviewservice.h \ + $$PWD/qqmlpreviewservicefactory.h + +OTHER_FILES += \ + $$PWD/qqmlpreviewservice.json + +PLUGIN_TYPE = qmltooling +PLUGIN_CLASS_NAME = QQmlPreviewServiceFactory + +load(qt_plugin) diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewblacklist.cpp b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewblacklist.cpp new file mode 100644 index 0000000000..d942740db3 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewblacklist.cpp @@ -0,0 +1,202 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QML preview debug service. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmlpreviewblacklist.h" + +QT_BEGIN_NAMESPACE + +void QQmlPreviewBlacklist::blacklist(const QString &path) +{ + if (!path.isEmpty()) + m_root.insert(path, 0); +} + +void QQmlPreviewBlacklist::whitelist(const QString &path) +{ + if (!path.isEmpty()) + m_root.remove(path, 0); +} + +bool QQmlPreviewBlacklist::isBlacklisted(const QString &path) const +{ + return path.isEmpty() ? true : m_root.containedPrefixLeaf(path, 0) > 0; +} + +void QQmlPreviewBlacklist::clear() +{ + m_root = Node(); +} + +QQmlPreviewBlacklist::Node::Node() +{ +} + +QQmlPreviewBlacklist::Node::Node(const QQmlPreviewBlacklist::Node &other) : + m_mine(other.m_mine), m_isLeaf(other.m_isLeaf) +{ + for (auto it = other.m_next.begin(), end = other.m_next.end(); it != end; ++it) + m_next.insert(it.key(), new Node(**it)); +} + +QQmlPreviewBlacklist::Node::Node(QQmlPreviewBlacklist::Node &&other) Q_DECL_NOEXCEPT +{ + m_mine.swap(other.m_mine); + m_next.swap(other.m_next); + m_isLeaf = other.m_isLeaf; +} + +QQmlPreviewBlacklist::Node::~Node() +{ + qDeleteAll(m_next); +} + +QQmlPreviewBlacklist::Node &QQmlPreviewBlacklist::Node::operator=( + const QQmlPreviewBlacklist::Node &other) +{ + if (&other != this) { + m_mine = other.m_mine; + for (auto it = other.m_next.begin(), end = other.m_next.end(); it != end; ++it) + m_next.insert(it.key(), new Node(**it)); + m_isLeaf = other.m_isLeaf; + } + return *this; +} + +QQmlPreviewBlacklist::Node &QQmlPreviewBlacklist::Node::operator=( + QQmlPreviewBlacklist::Node &&other) Q_DECL_NOEXCEPT +{ + if (&other != this) { + m_mine.swap(other.m_mine); + m_next.swap(other.m_next); + m_isLeaf = other.m_isLeaf; + } + return *this; +} + +void QQmlPreviewBlacklist::Node::split(QString::iterator it, QString::iterator end) +{ + QString existing; + existing.resize(end - it - 1); + std::copy(it + 1, end, existing.begin()); + + Node *node = new Node(existing, m_next, m_isLeaf); + m_next.clear(); + m_next.insert(*it, node); + m_mine.resize(it - m_mine.begin()); + m_isLeaf = false; +} + +void QQmlPreviewBlacklist::Node::insert(const QString &path, int offset) +{ + for (auto it = m_mine.begin(), end = m_mine.end(); it != end; ++it) { + if (offset == path.size()) { + split(it, end); + m_isLeaf = true; + return; + } + + if (path.at(offset) != *it) { + split(it, end); + + QString inserted; + inserted.resize(path.size() - offset - 1); + std::copy(path.begin() + offset + 1, path.end(), inserted.begin()); + m_next.insert(path.at(offset), new Node(inserted)); + return; + } + + ++offset; + } + + if (offset == path.size()) { + m_isLeaf = true; + return; + } + + Node *&node = m_next[path.at(offset++)]; + if (node == nullptr) { + QString inserted; + inserted.resize(path.size() - offset); + std::copy(path.begin() + offset, path.end(), inserted.begin()); + node = new Node(inserted); + } else { + node->insert(path, offset); + } +} + +void QQmlPreviewBlacklist::Node::remove(const QString &path, int offset) +{ + for (auto it = m_mine.begin(), end = m_mine.end(); it != end; ++it) { + if (offset == path.size() || path.at(offset) != *it) { + split(it, end); + return; + } + ++offset; + } + + m_isLeaf = false; + if (offset == path.size()) + return; + + auto it = m_next.find(path.at(offset)); + if (it != m_next.end()) + (*it)->remove(path, ++offset); +} + +int QQmlPreviewBlacklist::Node::containedPrefixLeaf(const QString &path, int offset) const +{ + if (offset == path.size()) + return (m_mine.isEmpty() && m_isLeaf) ? offset : -1; + + for (auto it = m_mine.begin(), end = m_mine.end(); it != end; ++it) { + if (path.at(offset) != *it) + return -1; + + if (++offset == path.size()) + return (++it == end && m_isLeaf) ? offset : -1; + } + + const QChar c = path.at(offset); + if (m_isLeaf && c == '/') + return offset; + + auto it = m_next.find(c); + if (it == m_next.end()) + return -1; + + return (*it)->containedPrefixLeaf(path, ++offset); +} + +QQmlPreviewBlacklist::Node::Node(const QString &mine, + const QHash<QChar, QQmlPreviewBlacklist::Node *> &next, + bool isLeaf) + : m_mine(mine), m_next(next), m_isLeaf(isLeaf) +{ +} + +QT_END_NAMESPACE diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewblacklist.h b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewblacklist.h new file mode 100644 index 0000000000..ab9c3a3d8a --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewblacklist.h @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QML preview debug service. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQMLPREVIEWBLACKLIST_H +#define QQMLPREVIEWBLACKLIST_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qhash.h> +#include <QtCore/qchar.h> +#include <QtCore/qstring.h> +#include <algorithm> + +QT_BEGIN_NAMESPACE + +class QQmlPreviewBlacklist +{ +public: + void blacklist(const QString &path); + void whitelist(const QString &path); + bool isBlacklisted(const QString &path) const; + void clear(); + +private: + class Node { + public: + Node(); + Node(const Node &other); + Node(Node &&other) Q_DECL_NOEXCEPT; + + ~Node(); + + Node &operator=(const Node &other); + Node &operator=(Node &&other) Q_DECL_NOEXCEPT; + + void split(QString::iterator it, QString::iterator end); + void insert(const QString &path, int offset); + void remove(const QString &path, int offset); + int containedPrefixLeaf(const QString &path, int offset) const; + + private: + Node(const QString &mine, const QHash<QChar, Node *> &next = QHash<QChar, Node *>(), + bool isLeaf = true); + + QString m_mine; + QHash<QChar, Node *> m_next; + bool m_isLeaf = false; + }; + + Node m_root; +}; + +QT_END_NAMESPACE + +#endif // QQMLPREVIEWBLACKLIST_H diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileengine.cpp b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileengine.cpp new file mode 100644 index 0000000000..f88cebf806 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileengine.cpp @@ -0,0 +1,432 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QML preview debug service. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmlpreviewfileengine.h" +#include "qqmlpreviewservice.h" + +#include <QtCore/qlibraryinfo.h> +#include <QtCore/qthread.h> +#include <QtCore/qwaitcondition.h> + +#include <cstring> + +QT_BEGIN_NAMESPACE + +static bool isRelative(const QString &path) +{ + if (path.isEmpty()) + return true; + if (path.at(0) == '/') + return false; + if (path.at(0) == ':' && path.length() >= 2 && path.at(1) == '/') + return false; +#ifdef Q_OS_WIN + if (path.length() >= 2 && path.at(1) == ':') + return false; +#endif + return true; +} + +static QString absolutePath(const QString &path) +{ + return QDir::cleanPath(isRelative(path) ? (QDir::currentPath() + '/' + path) : path); +} + +bool isRootPath(const QString &path) +{ + return QFileSystemEntry::isRootPath(path); +} + +class QQmlPreviewFileEngineIterator : public QAbstractFileEngineIterator +{ +public: + QQmlPreviewFileEngineIterator(QDir::Filters filters, const QStringList &filterNames, + const QStringList &m_entries); + ~QQmlPreviewFileEngineIterator(); + + QString next() override; + bool hasNext() const override; + QString currentFileName() const override; + +private: + const QStringList m_entries; + int m_index; +}; + +QQmlPreviewFileEngineIterator::QQmlPreviewFileEngineIterator(QDir::Filters filters, + const QStringList &filterNames, + const QStringList &entries) + : QAbstractFileEngineIterator(filters, filterNames), m_entries(entries), m_index(0) +{ +} + +QQmlPreviewFileEngineIterator::~QQmlPreviewFileEngineIterator() +{ +} + +QString QQmlPreviewFileEngineIterator::next() +{ + if (!hasNext()) + return QString(); + ++m_index; + return currentFilePath(); +} + +bool QQmlPreviewFileEngineIterator::hasNext() const +{ + return m_index < m_entries.size(); +} + +QString QQmlPreviewFileEngineIterator::currentFileName() const +{ + if (m_index == 0 || m_index > m_entries.size()) + return QString(); + return m_entries.at(m_index - 1); +} + +QQmlPreviewFileEngine::QQmlPreviewFileEngine(const QString &file, const QString &absolute, + QQmlPreviewFileLoader *loader) : + m_name(file), m_absolute(absolute), m_loader(loader) +{ + load(); +} + +void QQmlPreviewFileEngine::setFileName(const QString &file) +{ + m_name = file; + m_absolute = absolutePath(file); + m_fallback.reset(); + m_contents.close(); + m_contents.setData(QByteArray()); + m_entries.clear(); + load(); +} + +bool QQmlPreviewFileEngine::open(QIODevice::OpenMode flags) +{ + switch (m_result) { + case QQmlPreviewFileLoader::File: + return m_contents.open(flags); + case QQmlPreviewFileLoader::Directory: + return false; + case QQmlPreviewFileLoader::Fallback: + return m_fallback->open(flags); + default: + Q_UNREACHABLE(); + return false; + } +} + +bool QQmlPreviewFileEngine::close() +{ + switch (m_result) { + case QQmlPreviewFileLoader::Fallback: + return m_fallback->close(); + case QQmlPreviewFileLoader::File: + m_contents.close(); + return true; + case QQmlPreviewFileLoader::Directory: + return false; + default: + Q_UNREACHABLE(); + return false; + } +} + +qint64 QQmlPreviewFileEngine::size() const +{ + return m_fallback ? m_fallback->size() : m_contents.size(); +} + +qint64 QQmlPreviewFileEngine::pos() const +{ + return m_fallback ? m_fallback->pos() : m_contents.pos(); +} + +bool QQmlPreviewFileEngine::seek(qint64 newPos) +{ + return m_fallback? m_fallback->seek(newPos) : m_contents.seek(newPos); +} + +qint64 QQmlPreviewFileEngine::read(char *data, qint64 maxlen) +{ + return m_fallback ? m_fallback->read(data, maxlen) : m_contents.read(data, maxlen); +} + +QAbstractFileEngine::FileFlags QQmlPreviewFileEngine::fileFlags( + QAbstractFileEngine::FileFlags type) const +{ + if (m_fallback) + return m_fallback->fileFlags(type); + + QAbstractFileEngine::FileFlags ret = 0; + + if (type & PermsMask) { + ret |= QAbstractFileEngine::FileFlags( + ReadOwnerPerm | ReadUserPerm | ReadGroupPerm | ReadOtherPerm); + } + + if (type & TypesMask) { + if (m_result == QQmlPreviewFileLoader::Directory) + ret |= DirectoryType; + else + ret |= FileType; + } + + if (type & FlagsMask) { + ret |= ExistsFlag; + if (isRootPath(m_name)) + ret |= RootFlag; + } + + return ret; +} + +QString QQmlPreviewFileEngine::fileName(QAbstractFileEngine::FileName file) const +{ + if (m_fallback) + return m_fallback->fileName(file); + + if (file == BaseName) { + int slashPos = m_name.lastIndexOf('/'); + if (slashPos == -1) + return m_name; + return m_name.mid(slashPos + 1); + } else if (file == PathName || file == AbsolutePathName) { + const QString path = (file == AbsolutePathName) ? m_absolute : m_name; + const int slashPos = path.lastIndexOf('/'); + if (slashPos == -1) + return QString(); + else if (slashPos == 0) + return "/"; + return path.left(slashPos); + } else if (file == CanonicalName || file == CanonicalPathName) { + if (file == CanonicalPathName) { + const int slashPos = m_absolute.lastIndexOf('/'); + if (slashPos != -1) + return m_absolute.left(slashPos); + } + return m_absolute; + } + return m_name; +} + +uint QQmlPreviewFileEngine::ownerId(QAbstractFileEngine::FileOwner owner) const +{ + return m_fallback ? m_fallback->ownerId(owner) : static_cast<uint>(-2); +} + +QAbstractFileEngine::Iterator *QQmlPreviewFileEngine::beginEntryList(QDir::Filters filters, + const QStringList &filterNames) +{ + return m_fallback ? m_fallback->beginEntryList(filters, filterNames) + : new QQmlPreviewFileEngineIterator(filters, filterNames, m_entries); +} + +QAbstractFileEngine::Iterator *QQmlPreviewFileEngine::endEntryList() +{ + return m_fallback ? m_fallback->endEntryList() : nullptr; +} + +bool QQmlPreviewFileEngine::flush() +{ + return m_fallback ? m_fallback->flush() : true; +} + +bool QQmlPreviewFileEngine::syncToDisk() +{ + return m_fallback ? m_fallback->syncToDisk() : false; +} + +bool QQmlPreviewFileEngine::isSequential() const +{ + return m_fallback ? m_fallback->isSequential() : m_contents.isSequential(); +} + +bool QQmlPreviewFileEngine::remove() +{ + return m_fallback ? m_fallback->remove() : false; +} + +bool QQmlPreviewFileEngine::copy(const QString &newName) +{ + return m_fallback ? m_fallback->copy(newName) : false; +} + +bool QQmlPreviewFileEngine::rename(const QString &newName) +{ + return m_fallback ? m_fallback->rename(newName) : false; +} + +bool QQmlPreviewFileEngine::renameOverwrite(const QString &newName) +{ + return m_fallback ? m_fallback->renameOverwrite(newName) : false; +} + +bool QQmlPreviewFileEngine::link(const QString &newName) +{ + return m_fallback ? m_fallback->link(newName) : false; +} + +bool QQmlPreviewFileEngine::mkdir(const QString &dirName, bool createParentDirectories) const +{ + return m_fallback ? m_fallback->mkdir(dirName, createParentDirectories) : false; +} + +bool QQmlPreviewFileEngine::rmdir(const QString &dirName, bool recurseParentDirectories) const +{ + return m_fallback ? m_fallback->rmdir(dirName, recurseParentDirectories) : false; +} + +bool QQmlPreviewFileEngine::setSize(qint64 size) +{ + switch (m_result) { + case QQmlPreviewFileLoader::Fallback: + return m_fallback->setSize(size); + case QQmlPreviewFileLoader::File: + if (size < 0 || size > std::numeric_limits<int>::max()) + return false; + m_contents.buffer().resize(static_cast<int>(size)); + return true; + case QQmlPreviewFileLoader::Directory: + return false; + default: + Q_UNREACHABLE(); + return false; + } +} + +bool QQmlPreviewFileEngine::caseSensitive() const +{ + return m_fallback ? m_fallback->caseSensitive() : true; +} + +bool QQmlPreviewFileEngine::isRelativePath() const +{ + return m_fallback ? m_fallback->isRelativePath() : isRelative(m_name); +} + +QStringList QQmlPreviewFileEngine::entryList(QDir::Filters filters, + const QStringList &filterNames) const +{ + return m_fallback ? m_fallback->entryList(filters, filterNames) + : QAbstractFileEngine::entryList(filters, filterNames); +} + +bool QQmlPreviewFileEngine::setPermissions(uint perms) +{ + return m_fallback ? m_fallback->setPermissions(perms) : false; +} + +QByteArray QQmlPreviewFileEngine::id() const +{ + return m_fallback ? m_fallback->id() : QByteArray(); +} + +QString QQmlPreviewFileEngine::owner(FileOwner owner) const +{ + return m_fallback ? m_fallback->owner(owner) : QString(); +} + +QDateTime QQmlPreviewFileEngine::fileTime(FileTime time) const +{ + // Files we replace are always newer than the ones we had before. This makes the QML engine + // actually recompile them, rather than pick them from the cache. + return m_fallback ? m_fallback->fileTime(time) : QDateTime::currentDateTime(); +} + +int QQmlPreviewFileEngine::handle() const +{ + return m_fallback ? m_fallback->handle() : -1; +} + +qint64 QQmlPreviewFileEngine::readLine(char *data, qint64 maxlen) +{ + return m_fallback ? m_fallback->readLine(data, maxlen) : m_contents.readLine(data, maxlen); +} + +qint64 QQmlPreviewFileEngine::write(const char *data, qint64 len) +{ + return m_fallback ? m_fallback->write(data, len) : m_contents.write(data, len); +} + +bool QQmlPreviewFileEngine::extension(Extension extension, const ExtensionOption *option, ExtensionReturn *output) +{ + return m_fallback ? m_fallback->extension(extension, option, output) : false; +} + +bool QQmlPreviewFileEngine::supportsExtension(Extension extension) const +{ + return m_fallback ? m_fallback->supportsExtension(extension) : false; +} + +void QQmlPreviewFileEngine::load() const +{ + m_result = m_loader->load(m_absolute); + switch (m_result) { + case QQmlPreviewFileLoader::File: + m_contents.setData(m_loader->contents()); + break; + case QQmlPreviewFileLoader::Directory: + m_entries = m_loader->entries(); + break; + case QQmlPreviewFileLoader::Fallback: + m_fallback.reset(QAbstractFileEngine::create(m_name)); + break; + case QQmlPreviewFileLoader::Unknown: + Q_UNREACHABLE(); + break; + } +} + +QQmlPreviewFileEngineHandler::QQmlPreviewFileEngineHandler(QQmlPreviewFileLoader *loader) + : m_loader(loader) +{ +} + +QAbstractFileEngine *QQmlPreviewFileEngineHandler::create(const QString &fileName) const +{ + // Don't load compiled QML/JS over the network + if (fileName.endsWith(".qmlc") || fileName.endsWith(".jsc") || isRootPath(fileName)) { + return nullptr; + } + + QString relative = fileName; + while (relative.endsWith('/')) + relative.chop(1); + + if (relative.isEmpty() || relative == ":") + return nullptr; + + const QString absolute = relative.startsWith(':') ? relative : absolutePath(relative); + + return m_loader->isBlacklisted(absolute) + ? nullptr : new QQmlPreviewFileEngine(relative, absolute, m_loader.data()); +} + +QT_END_NAMESPACE diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileengine.h b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileengine.h new file mode 100644 index 0000000000..60af76c334 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileengine.h @@ -0,0 +1,125 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QML preview debug service. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQMLPREVIEWFILEENGINE_H +#define QQMLPREVIEWFILEENGINE_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qqmlpreviewfileloader.h" + +#include <private/qabstractfileengine_p.h> +#include <private/qfsfileengine_p.h> +#include <QtCore/qbuffer.h> + +QT_BEGIN_NAMESPACE + +class QQmlPreviewFileEngine : public QAbstractFileEngine +{ +public: + QQmlPreviewFileEngine(const QString &file, const QString &absolute, + QQmlPreviewFileLoader *loader); + + void setFileName(const QString &file) override; + + bool open(QIODevice::OpenMode flags) override ; + bool close() override; + qint64 size() const override; + qint64 pos() const override; + bool seek(qint64) override; + qint64 read(char *data, qint64 maxlen) override; + + FileFlags fileFlags(FileFlags type) const override; + QString fileName(QAbstractFileEngine::FileName file) const override; + uint ownerId(FileOwner) const override; + + Iterator *beginEntryList(QDir::Filters filters, const QStringList &filterNames) override; + Iterator *endEntryList() override; + + // Forwarding to fallback if exists + bool flush() override; + bool syncToDisk() override; + bool isSequential() const override; + bool remove() override; + bool copy(const QString &newName) override; + bool rename(const QString &newName) override; + bool renameOverwrite(const QString &newName) override; + bool link(const QString &newName) override; + bool mkdir(const QString &dirName, bool createParentDirectories) const override; + bool rmdir(const QString &dirName, bool recurseParentDirectories) const override; + bool setSize(qint64 size) override; + bool caseSensitive() const override; + bool isRelativePath() const override; + QStringList entryList(QDir::Filters filters, const QStringList &filterNames) const override; + bool setPermissions(uint perms) override; + QByteArray id() const override; + QString owner(FileOwner) const override; + QDateTime fileTime(FileTime time) const override; + int handle() const override; + qint64 readLine(char *data, qint64 maxlen) override; + qint64 write(const char *data, qint64 len) override; + bool extension(Extension extension, const ExtensionOption *option, ExtensionReturn *output) override; + bool supportsExtension(Extension extension) const override; + +private: + void load() const; + + QString m_name; + QString m_absolute; + QPointer<QQmlPreviewFileLoader> m_loader; + + mutable QBuffer m_contents; + mutable QStringList m_entries; + mutable QScopedPointer<QAbstractFileEngine> m_fallback; + mutable QQmlPreviewFileLoader::Result m_result = QQmlPreviewFileLoader::Unknown; +}; + +class QQmlPreviewFileEngineHandler : public QAbstractFileEngineHandler +{ +public: + QQmlPreviewFileEngineHandler(QQmlPreviewFileLoader *loader); + QAbstractFileEngine *create(const QString &fileName) const override; + +private: + QPointer<QQmlPreviewFileLoader> m_loader; +}; + + + +QT_END_NAMESPACE + +#endif // QQMLPREVIEWFILEENGINE_H diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileloader.cpp b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileloader.cpp new file mode 100644 index 0000000000..17a3a48184 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileloader.cpp @@ -0,0 +1,185 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QML preview debug service. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmlpreviewfileloader.h" +#include "qqmlpreviewservice.h" + +#include <QtCore/qlibraryinfo.h> +#include <QtCore/qstandardpaths.h> + +QT_BEGIN_NAMESPACE + +QQmlPreviewFileLoader::QQmlPreviewFileLoader(QQmlPreviewServiceImpl *service) : m_service(service) +{ + // Exclude some resource paths used by Qt itself. There is no point in loading those from the + // client as the client will not have the files (or even worse, it may have different ones). + m_blacklist.blacklist(":/qt-project.org"); + m_blacklist.blacklist(":/QtQuick/Controls/Styles"); + m_blacklist.blacklist(":/ExtrasImports/QtQuick/Controls/Styles"); + m_blacklist.blacklist(":/qgradient"); + + // Target specific configuration should not replaced with files from the host. + m_blacklist.blacklist("/etc"); + + for (int loc = QLibraryInfo::PrefixPath; loc <= QLibraryInfo::TestsPath; ++loc) { + m_blacklist.blacklist(QLibraryInfo::location( + static_cast<QLibraryInfo::LibraryLocation>(loc))); + } + m_blacklist.blacklist(QLibraryInfo::location(QLibraryInfo::SettingsPath)); + + static const QStandardPaths::StandardLocation blackListLocations[] = { + QStandardPaths::DataLocation, + QStandardPaths::CacheLocation, + QStandardPaths::GenericDataLocation, + QStandardPaths::RuntimeLocation, + QStandardPaths::ConfigLocation, + QStandardPaths::GenericCacheLocation, + QStandardPaths::GenericConfigLocation, + QStandardPaths::AppDataLocation, + QStandardPaths::AppConfigLocation + }; + + for (auto locationType : blackListLocations) { + const QStringList locations = QStandardPaths::standardLocations(locationType); + for (const QString &location : locations) + m_blacklist.blacklist(location); + } + + connect(this, &QQmlPreviewFileLoader::request, service, &QQmlPreviewServiceImpl::forwardRequest, + Qt::DirectConnection); + connect(service, &QQmlPreviewServiceImpl::directory, this, &QQmlPreviewFileLoader::directory); + connect(service, &QQmlPreviewServiceImpl::file, this, &QQmlPreviewFileLoader::file); + connect(service, &QQmlPreviewServiceImpl::error, this, &QQmlPreviewFileLoader::error); + connect(service, &QQmlPreviewServiceImpl::clearCache, this, &QQmlPreviewFileLoader::clearCache); + moveToThread(&m_thread); + m_thread.start(); +} + +QQmlPreviewFileLoader::~QQmlPreviewFileLoader() { + m_thread.quit(); + m_thread.wait(); +} + +QQmlPreviewFileLoader::Result QQmlPreviewFileLoader::load(const QString &path) +{ + QMutexLocker locker(&m_mutex); + m_path = path; + + auto fileIterator = m_fileCache.constFind(path); + if (fileIterator != m_fileCache.constEnd()) { + m_result = File; + m_contents = *fileIterator; + m_entries.clear(); + return m_result; + } + + auto dirIterator = m_directoryCache.constFind(path); + if (dirIterator != m_directoryCache.constEnd()) { + m_result = Directory; + m_contents.clear(); + m_entries = *dirIterator; + return m_result; + } + + m_result = Unknown; + m_entries.clear(); + m_contents.clear(); + emit request(path); + m_waitCondition.wait(&m_mutex); + return m_result; +} + +QByteArray QQmlPreviewFileLoader::contents() +{ + QMutexLocker locker(&m_mutex); + return m_contents; +} + +QStringList QQmlPreviewFileLoader::entries() +{ + QMutexLocker locker(&m_mutex); + return m_entries; +} + +void QQmlPreviewFileLoader::whitelist(const QUrl &url) +{ + const QString path = QQmlFile::urlToLocalFileOrQrc(url); + if (!path.isEmpty()) { + QMutexLocker locker(&m_mutex); + m_blacklist.whitelist(path); + } +} + +bool QQmlPreviewFileLoader::isBlacklisted(const QString &path) +{ + QMutexLocker locker(&m_mutex); + return m_blacklist.isBlacklisted(path); +} + +void QQmlPreviewFileLoader::file(const QString &path, const QByteArray &contents) +{ + QMutexLocker locker(&m_mutex); + m_blacklist.whitelist(path); + m_fileCache[path] = contents; + if (path == m_path) { + m_contents = contents; + m_result = File; + m_waitCondition.wakeOne(); + } +} + +void QQmlPreviewFileLoader::directory(const QString &path, const QStringList &entries) +{ + QMutexLocker locker(&m_mutex); + m_blacklist.whitelist(path); + m_directoryCache[path] = entries; + if (path == m_path) { + m_entries = entries; + m_result = Directory; + m_waitCondition.wakeOne(); + } +} + +void QQmlPreviewFileLoader::error(const QString &path) +{ + QMutexLocker locker(&m_mutex); + m_blacklist.blacklist(path); + if (path == m_path) { + m_result = Fallback; + m_waitCondition.wakeOne(); + } +} + +void QQmlPreviewFileLoader::clearCache() +{ + QMutexLocker locker(&m_mutex); + m_fileCache.clear(); + m_directoryCache.clear(); +} + +QT_END_NAMESPACE diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileloader.h b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileloader.h new file mode 100644 index 0000000000..de0a9cadea --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileloader.h @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QML preview debug service. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQMLPREVIEWFILELOADER_H +#define QQMLPREVIEWFILELOADER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qqmlpreviewblacklist.h" + +#include <QtCore/qobject.h> +#include <QtCore/qthread.h> +#include <QtCore/qmutex.h> +#include <QtCore/qwaitcondition.h> +#include <QtCore/qvector.h> +#include <QtCore/qurl.h> +#include <QtCore/qpointer.h> +#include <QtCore/qset.h> + +QT_BEGIN_NAMESPACE + +class QQmlPreviewServiceImpl; +class QQmlPreviewFileLoader : public QObject +{ + Q_OBJECT +public: + enum Result { + File, + Directory, + Fallback, + Unknown + }; + + QQmlPreviewFileLoader(QQmlPreviewServiceImpl *service); + ~QQmlPreviewFileLoader(); + + Result load(const QString &file); + QByteArray contents(); + QStringList entries(); + + void whitelist(const QUrl &url); + bool isBlacklisted(const QString &file); + +signals: + void request(const QString &file); + +private: + QMutex m_mutex; + QWaitCondition m_waitCondition; + + QThread m_thread; + QPointer<QQmlPreviewServiceImpl> m_service; + + QString m_path; + QByteArray m_contents; + QStringList m_entries; + Result m_result; + + QQmlPreviewBlacklist m_blacklist; + QHash<QString, QByteArray> m_fileCache; + QHash<QString, QStringList> m_directoryCache; + + void file(const QString &file, const QByteArray &contents); + void directory(const QString &file, const QStringList &entries); + void error(const QString &file); + void clearCache(); +}; + +QT_END_NAMESPACE + +#endif // QQMLPREVIEWFILELOADER_H diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewhandler.cpp b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewhandler.cpp new file mode 100644 index 0000000000..fdfa9dcc34 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewhandler.cpp @@ -0,0 +1,313 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QML preview debug service. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmlpreviewhandler.h" + +#include <QtCore/qtimer.h> +#include <QtCore/qsettings.h> +#include <QtGui/qwindow.h> +#include <QtGui/qguiapplication.h> +#include <QtQuick/qquickwindow.h> +#include <QtQuick/qquickitem.h> +#include <QtQml/qqmlcomponent.h> + +#include <private/qquickpixmapcache_p.h> +#include <private/qquickview_p.h> +#include <private/qhighdpiscaling_p.h> + +QT_BEGIN_NAMESPACE + +struct QuitLockDisabler +{ + const bool quitLockEnabled; + + QuitLockDisabler() : quitLockEnabled(QCoreApplication::isQuitLockEnabled()) + { + QCoreApplication::setQuitLockEnabled(false); + } + + ~QuitLockDisabler() + { + QCoreApplication::setQuitLockEnabled(quitLockEnabled); + } +}; + +QQmlPreviewHandler::QQmlPreviewHandler(QObject *parent) : QObject(parent) +{ + m_dummyItem.reset(new QQuickItem); + + // TODO: Is there a better way to determine this? We want to keep the window alive when possible + // as otherwise it will reappear in a different place when (re)loading a file. However, + // the file we load might create another window, in which case the eglfs plugin (and + // others?) will do a qFatal as it only supports a single window. + const QString platformName = QGuiApplication::platformName(); + m_supportsMultipleWindows = (platformName == QStringLiteral("windows") + || platformName == QStringLiteral("cocoa") + || platformName == QStringLiteral("xcb") + || platformName == QStringLiteral("wayland")); + + QCoreApplication::instance()->installEventFilter(this); +} + +QQmlPreviewHandler::~QQmlPreviewHandler() +{ + clear(); +} + +static void closeAllWindows() +{ + const QWindowList windows = QGuiApplication::allWindows(); + for (QWindow *window : windows) + window->close(); +} + +bool QQmlPreviewHandler::eventFilter(QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::Show) { + if (QWindow *window = qobject_cast<QQuickWindow*>(obj)) { + m_lastPosition.initLastSavedWindowPosition(window); + } + } + if ((event->type() == QEvent::Move || event->type() == QEvent::Resize) && + qobject_cast<QQuickWindow*>(obj) == m_currentWindow) { + // we always start with factor 1 so calculate and save the origin as it would be not scaled + m_lastPosition.setPosition(m_currentWindow->framePosition() * + QHighDpiScaling::factor(m_currentWindow)); + } + + return QObject::eventFilter(obj, event); +} + +void QQmlPreviewHandler::addEngine(QQmlEngine *qmlEngine) +{ + m_engines.append(qmlEngine); +} + +void QQmlPreviewHandler::removeEngine(QQmlEngine *qmlEngine) +{ + const bool found = m_engines.removeOne(qmlEngine); + Q_ASSERT(found); + for (QObject *obj : m_createdObjects) + if (obj && QtQml::qmlEngine(obj) == qmlEngine) + delete obj; + m_createdObjects.removeAll(nullptr); +} + +void QQmlPreviewHandler::loadUrl(const QUrl &url) +{ + QSharedPointer<QuitLockDisabler> disabler(new QuitLockDisabler); + + clear(); + m_component.reset(nullptr); + QQuickPixmap::purgeCache(); + + const int numEngines = m_engines.count(); + if (numEngines > 1) { + emit error(QString::fromLatin1("%1 QML engines available. We cannot decide which one " + "should load the component.").arg(numEngines)); + return; + } else if (numEngines == 0) { + emit error(QLatin1String("No QML engines found.")); + return; + } + m_lastPosition.loadWindowPositionSettings(url); + + QQmlEngine *engine = m_engines.front(); + engine->clearComponentCache(); + m_component.reset(new QQmlComponent(engine, url, this)); + + auto onStatusChanged = [disabler, this](QQmlComponent::Status status) { + switch (status) { + case QQmlComponent::Null: + case QQmlComponent::Loading: + return true; // try again later + case QQmlComponent::Ready: + tryCreateObject(); + break; + case QQmlComponent::Error: + emit error(m_component->errorString()); + break; + default: + Q_UNREACHABLE(); + break; + } + + disconnect(m_component.data(), &QQmlComponent::statusChanged, this, nullptr); + return false; // we're done + }; + + if (onStatusChanged(m_component->status())) + connect(m_component.data(), &QQmlComponent::statusChanged, this, onStatusChanged); +} + +void QQmlPreviewHandler::rerun() +{ + if (m_component.isNull() || !m_component->isReady()) + emit error(QLatin1String("Component is not ready.")); + + QuitLockDisabler disabler; + Q_UNUSED(disabler); + clear(); + tryCreateObject(); +} + +void QQmlPreviewHandler::zoom(qreal newFactor) +{ + if (!m_currentWindow) + return; + if (qFuzzyIsNull(newFactor)) { + emit error(QString::fromLatin1("Zooming with factor: %1 will result in nothing " \ + "so it will be ignored.").arg(newFactor)); + return; + } + QString errorMessage; + bool resetZoom = false; + + if (newFactor < 0) { + resetZoom = true; + newFactor = 1.0; + } + + // On single-window devices we allow any scale factor as the window will adapt to the screen. + if (m_supportsMultipleWindows) { + const QSize newAvailableScreenSize = QQmlPreviewPosition::currentScreenSize(m_currentWindow) + * QHighDpiScaling::factor(m_currentWindow) / newFactor; + if (m_currentWindow->size().width() > newAvailableScreenSize.width()) { + errorMessage = QString::fromLatin1( + "Zooming with factor: " + "%1 will result in a too wide preview.").arg(newFactor); + } + if (m_currentWindow->size().height() > newAvailableScreenSize.height()) { + errorMessage = QString::fromLatin1( + "Zooming with factor: " + "%1 will result in a too heigh preview.").arg(newFactor); + } + } + + if (errorMessage.isEmpty()) { + const QPoint newToOriginMappedPosition = m_currentWindow->position() * + QHighDpiScaling::factor(m_currentWindow) / newFactor; + m_currentWindow->destroy(); + QHighDpiScaling::setScreenFactor(m_currentWindow->screen(), newFactor); + if (resetZoom) + QHighDpiScaling::updateHighDpiScaling(); + m_currentWindow->setPosition(newToOriginMappedPosition); + m_currentWindow->show(); + } else { + emit error(errorMessage); + } +} + +void QQmlPreviewHandler::clear() +{ + qDeleteAll(m_createdObjects); + m_createdObjects.clear(); + m_currentWindow = nullptr; +} + +Qt::WindowFlags fixFlags(Qt::WindowFlags flags) +{ + // If only the type flag is given, some other window flags are automatically assumed. When we + // add a flag, we need to make those explicit. + switch (flags) { + case Qt::Window: + return flags | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint + | Qt::WindowMinimizeButtonHint | Qt::WindowMaximizeButtonHint; + case Qt::Dialog: + case Qt::Tool: + return flags | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint; + default: + return flags; + } +} + +void QQmlPreviewHandler::showObject(QObject *object) +{ + if (QWindow *window = qobject_cast<QWindow *>(object)) { + m_currentWindow = qobject_cast<QQuickWindow *>(window); + for (QWindow *otherWindow : QGuiApplication::allWindows()) { + if (QQuickWindow *quickWindow = qobject_cast<QQuickWindow *>(otherWindow)) { + if (quickWindow == m_currentWindow) + continue; + quickWindow->setVisible(false); + quickWindow->setFlags(quickWindow->flags() & ~Qt::WindowStaysOnTopHint); + } + } + } else if (QQuickItem *item = qobject_cast<QQuickItem *>(object)) { + m_currentWindow = nullptr; + for (QWindow *window : QGuiApplication::allWindows()) { + if (QQuickWindow *quickWindow = qobject_cast<QQuickWindow *>(window)) { + if (m_currentWindow != nullptr) { + emit error(QLatin1String("Multiple QQuickWindows available. We cannot " + "decide which one to use.")); + return; + } + m_currentWindow = quickWindow; + } else { + window->setVisible(false); + window->setFlag(Qt::WindowStaysOnTopHint, false); + } + } + + if (m_currentWindow == nullptr) { + m_currentWindow = new QQuickWindow; + m_createdObjects.append(m_currentWindow.data()); + } + + for (QQuickItem *oldItem : m_currentWindow->contentItem()->childItems()) + oldItem->setParentItem(m_dummyItem.data()); + + // Special case for QQuickView, as that keeps a "root" pointer around, and uses it to + // automatically resize the window or the item. + if (QQuickView *view = qobject_cast<QQuickView *>(m_currentWindow)) + QQuickViewPrivate::get(view)->setRootObject(item); + else + item->setParentItem(m_currentWindow->contentItem()); + + m_currentWindow->resize(item->size().toSize()); + } else { + emit error(QLatin1String("Created object is neither a QWindow nor a QQuickItem.")); + } + + if (m_currentWindow) { + m_lastPosition.initLastSavedWindowPosition(m_currentWindow); + m_currentWindow->setFlags(fixFlags(m_currentWindow->flags()) | Qt::WindowStaysOnTopHint); + m_currentWindow->setVisible(true); + } +} + +void QQmlPreviewHandler::tryCreateObject() +{ + if (!m_supportsMultipleWindows) + closeAllWindows(); + QObject *object = m_component->create(); + m_createdObjects.append(object); + showObject(object); +} + +QT_END_NAMESPACE diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewhandler.h b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewhandler.h new file mode 100644 index 0000000000..78099fb1e5 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewhandler.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QML preview debug service. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQMLPREVIEWHANDLER_H +#define QQMLPREVIEWHANDLER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qqmlpreviewposition.h" + +#include <QtCore/qobject.h> +#include <QtCore/qvector.h> +#include <QtCore/qrect.h> +#include <QtCore/qpointer.h> +#include <QtQml/qqmlengine.h> + +QT_BEGIN_NAMESPACE + +class QQmlEngine; +class QQuickItem; +class QQmlPreviewUrlInterceptor; +class QQuickWindow; +class QQmlPreviewHandler : public QObject +{ + Q_OBJECT +public: + explicit QQmlPreviewHandler(QObject *parent = nullptr); + ~QQmlPreviewHandler(); + + void addEngine(QQmlEngine *engine); + void removeEngine(QQmlEngine *engine); + + void loadUrl(const QUrl &url); + void rerun(); + void zoom(qreal newFactor); + + void clear(); + +signals: + void error(const QString &message); +protected: + bool eventFilter(QObject *obj, QEvent *event); +private: + void tryCreateObject(); + void showObject(QObject *object); + + QScopedPointer<QQuickItem> m_dummyItem; + QList<QQmlEngine *> m_engines; + QVector<QPointer<QObject>> m_createdObjects; + QScopedPointer<QQmlComponent> m_component; + QPointer<QQuickWindow> m_currentWindow; + bool m_supportsMultipleWindows; + QQmlPreviewPosition m_lastPosition; +}; + +QT_END_NAMESPACE + +#endif // QQMLPREVIEWHANDLER_H diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewposition.cpp b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewposition.cpp new file mode 100644 index 0000000000..2382f72fe3 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewposition.cpp @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QML preview debug service. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmlpreviewposition.h" + +#include <QtGui/qwindow.h> +#include <QtGui/qscreen.h> +#include <QtGui/qguiapplication.h> + +QT_BEGIN_NAMESPACE + +static const QSize availableScreenSize(const QPoint &point) +{ + if (const QScreen *screen = QGuiApplication::screenAt(point)) + return screen->availableGeometry().size(); + return QSize(); +} + +QQmlPreviewPosition::QQmlPreviewPosition() + : m_settings("QtProject", "QtQmlPreview") +{ + m_savePositionTimer.setSingleShot(true); + m_savePositionTimer.setInterval(500); + QObject::connect(&m_savePositionTimer, &QTimer::timeout, [this]() { + saveWindowPosition(); + }); +} + +void QQmlPreviewPosition::setPosition(const QPoint &point) +{ + m_hasPosition = true; + m_lastWindowPosition = point; + m_savePositionTimer.start(); +} + +void QQmlPreviewPosition::saveWindowPosition() +{ + if (m_hasPosition) { + if (!m_settingsKey.isNull()) + m_settings.setValue(m_settingsKey, m_lastWindowPosition); + + m_settings.setValue(QLatin1String("global_lastpostion"), m_lastWindowPosition); + } +} + +void QQmlPreviewPosition::loadWindowPositionSettings(const QUrl &url) +{ + m_settingsKey = url.toString(QUrl::PreferLocalFile) + QLatin1String("_lastpostion"); + + if (m_settings.contains(m_settingsKey)) { + m_hasPosition = true; + m_lastWindowPosition = m_settings.value(m_settingsKey).toPoint(); + } +} + +void QQmlPreviewPosition::initLastSavedWindowPosition(QWindow *window) +{ + if (m_positionedWindows.contains(window)) + return; + if (!m_hasPosition) { + // in case there was nothing saved, we do not want to set anything + if (!m_settings.contains(QLatin1String("global_lastpostion"))) + return; + m_lastWindowPosition = m_settings.value(QLatin1String("global_lastpostion")).toPoint(); + } + if (QGuiApplication::screenAt(m_lastWindowPosition)) + window->setFramePosition(m_lastWindowPosition); + + m_positionedWindows.append(window); +} + +const QSize QQmlPreviewPosition::currentScreenSize(QWindow *window) +{ + return availableScreenSize(window->position()); +} + +QT_END_NAMESPACE diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewposition.h b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewposition.h new file mode 100644 index 0000000000..8683757007 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewposition.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QML preview debug service. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQMLPREVIEWPOSITION_H +#define QQMLPREVIEWPOSITION_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qvector.h> +#include <QtCore/qpoint.h> +#include <QtCore/qurl.h> +#include <QtCore/qtimer.h> +#include <QtCore/qsettings.h> + +QT_BEGIN_NAMESPACE + +class QWindow; + +class QQmlPreviewPosition +{ +public: + QQmlPreviewPosition(); + + void setPosition(const QPoint &point); + void saveWindowPosition(); + void loadWindowPositionSettings(const QUrl &url); + void initLastSavedWindowPosition(QWindow *window); + static const QSize currentScreenSize(QWindow *window); + +private: + bool m_hasPosition = false; + QPoint m_lastWindowPosition; + QSettings m_settings; + QString m_settingsKey; + QTimer m_savePositionTimer; + QVector<QWindow *> m_positionedWindows; +}; + + +QT_END_NAMESPACE + +#endif // QQMLPREVIEWPOSITION_H diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservice.cpp b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservice.cpp new file mode 100644 index 0000000000..70e895e78c --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservice.cpp @@ -0,0 +1,165 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QML preview debug service. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmlpreviewservice.h" + +#include <QtCore/qpointer.h> +#include <QtQml/qqmlengine.h> +#include <QtQml/qqmlcomponent.h> +#include <QtQuick/qquickwindow.h> +#include <QtQuick/qquickitem.h> +#include <QtGui/qguiapplication.h> + +#include <private/qquickpixmapcache_p.h> +#include <private/qqmldebugconnector_p.h> +#include <private/qversionedpacket_p.h> + +QT_BEGIN_NAMESPACE + +const QString QQmlPreviewServiceImpl::s_key = QStringLiteral("QmlPreview"); +using QQmlDebugPacket = QVersionedPacket<QQmlDebugConnector>; + +QQmlPreviewServiceImpl::QQmlPreviewServiceImpl(QObject *parent) : + QQmlDebugService(s_key, 1.0f, parent) +{ + m_loader.reset(new QQmlPreviewFileLoader(this)); + connect(this, &QQmlPreviewServiceImpl::load, + m_loader.data(), &QQmlPreviewFileLoader::whitelist, Qt::DirectConnection); + connect(this, &QQmlPreviewServiceImpl::load, &m_handler, &QQmlPreviewHandler::loadUrl); + connect(this, &QQmlPreviewServiceImpl::rerun, &m_handler, &QQmlPreviewHandler::rerun); + connect(this, &QQmlPreviewServiceImpl::zoom, &m_handler, &QQmlPreviewHandler::zoom); + connect(&m_handler, &QQmlPreviewHandler::error, this, &QQmlPreviewServiceImpl::forwardError, + Qt::DirectConnection); + +} + +QQmlPreviewServiceImpl::~QQmlPreviewServiceImpl() +{ +} + +void QQmlPreviewServiceImpl::messageReceived(const QByteArray &data) +{ + QQmlDebugPacket packet(data); + qint8 command; + + packet >> command; + switch (command) { + case File: { + QString path; + QByteArray contents; + packet >> path >> contents; + emit file(path, contents); + + // Replace the whole scene with the first file successfully loaded over the debug + // connection. This is an OK approximation of the root component, and if the client wants + // something specific, it will send an explicit Load anyway. + if (m_currentUrl.isEmpty() && path.endsWith(".qml")) { + if (path.startsWith(':')) + m_currentUrl = QUrl("qrc" + path); + else + m_currentUrl = QUrl::fromLocalFile(path); + emit load(m_currentUrl); + } + break; + } + case Directory: { + QString path; + QStringList entries; + packet >> path >> entries; + emit directory(path, entries); + break; + } + case Load: { + QUrl url; + packet >> url; + if (url.isEmpty()) + url = m_currentUrl; + else + m_currentUrl = url; + emit load(url); + break; + } + case Error: { + QString file; + packet >> file; + emit error(file); + break; + } + case Rerun: + emit rerun(); + break; + case ClearCache: + emit clearCache(); + break; + case Zoom: { + float factor; + packet >> factor; + emit zoom(static_cast<qreal>(factor)); + break; + } + default: + forwardError(QString::fromLatin1("Invalid command: %1").arg(command)); + break; + } +} + +void QQmlPreviewServiceImpl::engineAboutToBeAdded(QJSEngine *engine) +{ + if (QQmlEngine *qmlEngine = qobject_cast<QQmlEngine *>(engine)) + m_handler.addEngine(qmlEngine); + emit attachedToEngine(engine); +} + +void QQmlPreviewServiceImpl::engineAboutToBeRemoved(QJSEngine *engine) +{ + if (QQmlEngine *qmlEngine = qobject_cast<QQmlEngine *>(engine)) + m_handler.removeEngine(qmlEngine); + emit detachedFromEngine(engine); +} + +void QQmlPreviewServiceImpl::stateChanged(QQmlDebugService::State state) +{ + m_fileEngine.reset(state == Enabled ? new QQmlPreviewFileEngineHandler(m_loader.data()) + : nullptr); +} + +void QQmlPreviewServiceImpl::forwardRequest(const QString &file) +{ + QQmlDebugPacket packet; + packet << static_cast<qint8>(Request) << file; + emit messageToClient(name(), packet.data()); +} + +void QQmlPreviewServiceImpl::forwardError(const QString &error) +{ + QQmlDebugPacket packet; + packet << static_cast<qint8>(Error) << error; + emit messageToClient(name(), packet.data()); +} + +QT_END_NAMESPACE diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservice.h b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservice.h new file mode 100644 index 0000000000..6c35ef52dd --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservice.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of th QML Preview debug service. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQMLPREVIEWSERVICE_H +#define QQMLPREVIEWSERVICE_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qqmlpreviewhandler.h" +#include "qqmlpreviewfileengine.h" +#include <private/qqmldebugserviceinterfaces_p.h> + +QT_BEGIN_NAMESPACE + +class QQmlPreviewFileEngineHandler; +class QQmlPreviewHandler; +class QQmlPreviewServiceImpl : public QQmlDebugService +{ + Q_OBJECT + +public: + enum Command { + File, + Load, + Request, + Error, + Rerun, + Directory, + ClearCache, + Zoom + }; + + static const QString s_key; + + QQmlPreviewServiceImpl(QObject *parent = nullptr); + virtual ~QQmlPreviewServiceImpl(); + + void messageReceived(const QByteArray &message) override; + void engineAboutToBeAdded(QJSEngine *engine) override; + void engineAboutToBeRemoved(QJSEngine *engine) override; + void stateChanged(State state) override; + + void forwardRequest(const QString &file); + void forwardError(const QString &error); + +signals: + void error(const QString &file); + void file(const QString &file, const QByteArray &contents); + void directory(const QString &file, const QStringList &entries); + void load(const QUrl &url); + void rerun(); + void clearCache(); + void zoom(qreal factor); + +private: + QScopedPointer<QQmlPreviewFileEngineHandler> m_fileEngine; + QScopedPointer<QQmlPreviewFileLoader> m_loader; + QQmlPreviewHandler m_handler; + QUrl m_currentUrl; +}; + +QT_END_NAMESPACE + +#endif // QQMLPREVIEWSERVICE_H diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservice.json b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservice.json new file mode 100644 index 0000000000..d7e1ef1f10 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservice.json @@ -0,0 +1,3 @@ +{ + "Keys" : [ "QmlPreview" ] +} diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservicefactory.cpp b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservicefactory.cpp new file mode 100644 index 0000000000..4e09bd1002 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservicefactory.cpp @@ -0,0 +1,39 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QML preview debug service. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmlpreviewservicefactory.h" +#include "qqmlpreviewservice.h" + +QT_BEGIN_NAMESPACE + +QQmlDebugService *QQmlPreviewServiceFactory::create(const QString &key) +{ + return key == QQmlPreviewServiceImpl::s_key ? new QQmlPreviewServiceImpl(this) : nullptr; +} + +QT_END_NAMESPACE diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservicefactory.h b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservicefactory.h new file mode 100644 index 0000000000..f51e696fe6 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservicefactory.h @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QML preview debug service. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQMLPREVIEWSERVCIEFACTORY_H +#define QQMLPREVIEWSERVCIEFACTORY_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <private/qqmldebugservicefactory_p.h> + +QT_BEGIN_NAMESPACE + +class QQmlPreviewServiceFactory : public QQmlDebugServiceFactory +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QQmlDebugServiceFactory_iid FILE "qqmlpreviewservice.json") + +public: + QQmlDebugService *create(const QString &key) override; +}; + +QT_END_NAMESPACE + +#endif // QQMLPREVIEWSERVCIEFACTORY_H diff --git a/src/plugins/qmltooling/qmldbg_profiler/qqmlprofileradapter.cpp b/src/plugins/qmltooling/qmldbg_profiler/qqmlprofileradapter.cpp index 19104927f2..a688e98b3f 100644 --- a/src/plugins/qmltooling/qmldbg_profiler/qqmlprofileradapter.cpp +++ b/src/plugins/qmltooling/qmldbg_profiler/qqmlprofileradapter.cpp @@ -80,8 +80,7 @@ void QQmlProfilerAdapter::init(QQmlProfilerService *service, QQmlProfiler *profi // convert to QByteArrays that can be sent to the debug client static void qQmlProfilerDataToByteArrays(const QQmlProfilerData &d, QQmlProfiler::LocationHash &locations, - QList<QByteArray> &messages, - bool trackLocations) + QList<QByteArray> &messages) { QQmlDebugPacket ds; Q_ASSERT_X((d.messageType & (1 << 31)) == 0, Q_FUNC_INFO, @@ -96,7 +95,7 @@ static void qQmlProfilerDataToByteArrays(const QQmlProfilerData &d, if (decodedMessageType == QQmlProfilerDefinitions::RangeEnd || decodedMessageType == QQmlProfilerDefinitions::RangeStart) { ds << d.time << decodedMessageType << static_cast<quint32>(d.detailType); - if (trackLocations && d.locationId != 0) + if (d.locationId != 0) ds << static_cast<qint64>(d.locationId); } else { auto i = locations.find(d.locationId); @@ -107,8 +106,7 @@ static void qQmlProfilerDataToByteArrays(const QQmlProfilerData &d, << static_cast<qint32>(i->location.column); if (d.messageType & (1 << QQmlProfilerDefinitions::RangeData)) { // Send both, location and data ... - if (trackLocations) - ds << static_cast<qint64>(d.locationId); + ds << static_cast<qint64>(d.locationId); messages.append(ds.squeezedData()); ds.clear(); ds << d.time << int(QQmlProfilerDefinitions::RangeData) @@ -116,10 +114,8 @@ static void qQmlProfilerDataToByteArrays(const QQmlProfilerData &d, << (i->location.sourceFile.isEmpty() ? i->url.toString() : i->location.sourceFile); } - if (trackLocations) { - ds << static_cast<qint64>(d.locationId); - locations.erase(i); // ... so that we can erase here without missing anything. - } + ds << static_cast<qint64>(d.locationId); + locations.erase(i); // ... so that we can erase here without missing anything. } else { // Skip RangeData and RangeLocation: We've already sent them continue; @@ -130,14 +126,13 @@ static void qQmlProfilerDataToByteArrays(const QQmlProfilerData &d, } } -qint64 QQmlProfilerAdapter::sendMessages(qint64 until, QList<QByteArray> &messages, - bool trackLocations) +qint64 QQmlProfilerAdapter::sendMessages(qint64 until, QList<QByteArray> &messages) { while (next != data.length()) { const QQmlProfilerData &nextData = data.at(next); if (nextData.time > until || messages.length() > s_numMessagesPerBatch) return nextData.time; - qQmlProfilerDataToByteArrays(nextData, locations, messages, trackLocations); + qQmlProfilerDataToByteArrays(nextData, locations, messages); ++next; } diff --git a/src/plugins/qmltooling/qmldbg_profiler/qqmlprofileradapter.h b/src/plugins/qmltooling/qmldbg_profiler/qqmlprofileradapter.h index b14b72d254..12544a19c2 100644 --- a/src/plugins/qmltooling/qmldbg_profiler/qqmlprofileradapter.h +++ b/src/plugins/qmltooling/qmldbg_profiler/qqmlprofileradapter.h @@ -61,8 +61,7 @@ class QQmlProfilerAdapter : public QQmlAbstractProfilerAdapter { public: QQmlProfilerAdapter(QQmlProfilerService *service, QQmlEnginePrivate *engine); QQmlProfilerAdapter(QQmlProfilerService *service, QQmlTypeLoader *loader); - qint64 sendMessages(qint64 until, QList<QByteArray> &messages, - bool trackLocations) override; + qint64 sendMessages(qint64 until, QList<QByteArray> &messages) override; void receiveData(const QVector<QQmlProfilerData> &new_data, const QQmlProfiler::LocationHash &locations); diff --git a/src/plugins/qmltooling/qmldbg_profiler/qqmlprofilerservice.cpp b/src/plugins/qmltooling/qmldbg_profiler/qqmlprofilerservice.cpp index 21a5663f59..462401a093 100644 --- a/src/plugins/qmltooling/qmldbg_profiler/qqmlprofilerservice.cpp +++ b/src/plugins/qmltooling/qmldbg_profiler/qqmlprofilerservice.cpp @@ -58,7 +58,7 @@ Q_QML_DEBUG_PLUGIN_LOADER(QQmlAbstractProfilerAdapter) QQmlProfilerServiceImpl::QQmlProfilerServiceImpl(QObject *parent) : QQmlConfigurableDebugService<QQmlProfilerService>(1, parent), - m_waitingForStop(false), m_useMessageTypes(false), m_globalEnabled(false), m_globalFeatures(0) + m_waitingForStop(false), m_globalEnabled(false), m_globalFeatures(0) { m_timer.start(); QQmlAbstractProfilerAdapter *quickAdapter = @@ -332,7 +332,7 @@ void QQmlProfilerServiceImpl::stopProfiling(QJSEngine *engine) m_waitingForStop = true; for (QQmlAbstractProfilerAdapter *profiler : qAsConst(reporting)) - profiler->reportData(m_useMessageTypes); + profiler->reportData(); for (QQmlAbstractProfilerAdapter *profiler : qAsConst(stopping)) profiler->stopProfiling(); @@ -367,8 +367,7 @@ void QQmlProfilerServiceImpl::sendMessages() m_startTimes.erase(m_startTimes.begin()); qint64 next = first->sendMessages(m_startTimes.isEmpty() ? std::numeric_limits<qint64>::max() : - m_startTimes.begin().key(), messages, - m_useMessageTypes); + m_startTimes.begin().key(), messages); if (next != -1) m_startTimes.insert(next, first); @@ -454,13 +453,15 @@ void QQmlProfilerServiceImpl::messageReceived(const QByteArray &message) &m_flushTimer, &QTimer::stop); } } + + bool useMessageTypes = false; if (!stream.atEnd()) - stream >> m_useMessageTypes; + stream >> useMessageTypes; // If engineId == -1 objectForId() and then the cast will return 0. - if (enabled) + if (enabled && useMessageTypes) // If the client doesn't support message types don't profile. startProfiling(qobject_cast<QJSEngine *>(objectForId(engineId)), features); - else + else if (!enabled) // On stopProfiling the client doesn't repeat useMessageTypes. stopProfiling(qobject_cast<QJSEngine *>(objectForId(engineId))); stopWaiting(); @@ -486,7 +487,7 @@ void QQmlProfilerServiceImpl::flush() } for (QQmlAbstractProfilerAdapter *profiler : qAsConst(reporting)) - profiler->reportData(m_useMessageTypes); + profiler->reportData(); } QT_END_NAMESPACE diff --git a/src/plugins/qmltooling/qmldbg_profiler/qqmlprofilerservice.h b/src/plugins/qmltooling/qmldbg_profiler/qqmlprofilerservice.h index 2b92a478c1..3791ab29ae 100644 --- a/src/plugins/qmltooling/qmldbg_profiler/qqmlprofilerservice.h +++ b/src/plugins/qmltooling/qmldbg_profiler/qqmlprofilerservice.h @@ -117,7 +117,6 @@ private: QElapsedTimer m_timer; QTimer m_flushTimer; bool m_waitingForStop; - bool m_useMessageTypes; bool m_globalEnabled; quint64 m_globalFeatures; diff --git a/src/plugins/qmltooling/qmldbg_profiler/qv4profileradapter.cpp b/src/plugins/qmltooling/qmldbg_profiler/qv4profileradapter.cpp index f1ac8ef998..e4f2f556fc 100644 --- a/src/plugins/qmltooling/qmldbg_profiler/qv4profileradapter.cpp +++ b/src/plugins/qmltooling/qmldbg_profiler/qv4profileradapter.cpp @@ -104,8 +104,7 @@ qint64 QV4ProfilerAdapter::finalizeMessages(qint64 until, QList<QByteArray> &mes return callNext == -1 ? memoryNext : qMin(callNext, memoryNext); } -qint64 QV4ProfilerAdapter::sendMessages(qint64 until, QList<QByteArray> &messages, - bool trackLocations) +qint64 QV4ProfilerAdapter::sendMessages(qint64 until, QList<QByteArray> &messages) { QQmlDebugPacket d; @@ -134,24 +133,17 @@ qint64 QV4ProfilerAdapter::sendMessages(qint64 until, QList<QByteArray> &message appendMemoryEvents(props.start, messages, d); auto location = m_functionLocations.find(props.id); - d << props.start << int(RangeStart) << int(Javascript); - if (trackLocations) - d << static_cast<qint64>(props.id); + d << props.start << int(RangeStart) << int(Javascript) << static_cast<qint64>(props.id); if (location != m_functionLocations.end()) { messages.push_back(d.squeezedData()); d.clear(); d << props.start << int(RangeLocation) << int(Javascript) << location->file << location->line - << location->column; - if (trackLocations) - d << static_cast<qint64>(props.id); + << location->column << static_cast<qint64>(props.id); messages.push_back(d.squeezedData()); d.clear(); - d << props.start << int(RangeData) << int(Javascript) << location->name; - - if (trackLocations) { - d << static_cast<qint64>(props.id); - m_functionLocations.erase(location); - } + d << props.start << int(RangeData) << int(Javascript) << location->name + << static_cast<qint64>(props.id); + m_functionLocations.erase(location); } messages.push_back(d.squeezedData()); d.clear(); diff --git a/src/plugins/qmltooling/qmldbg_profiler/qv4profileradapter.h b/src/plugins/qmltooling/qmldbg_profiler/qv4profileradapter.h index 2211c82fc5..c4ca38d9b0 100644 --- a/src/plugins/qmltooling/qmldbg_profiler/qv4profileradapter.h +++ b/src/plugins/qmltooling/qmldbg_profiler/qv4profileradapter.h @@ -68,8 +68,7 @@ class QV4ProfilerAdapter : public QQmlAbstractProfilerAdapter { public: QV4ProfilerAdapter(QQmlProfilerService *service, QV4::ExecutionEngine *engine); - virtual qint64 sendMessages(qint64 until, QList<QByteArray> &messages, - bool trackLocations) override; + virtual qint64 sendMessages(qint64 until, QList<QByteArray> &messages) override; void receiveData(const QV4::Profiling::FunctionLocationHash &, const QVector<QV4::Profiling::FunctionCallProperties> &, diff --git a/src/plugins/qmltooling/qmldbg_quickprofiler/qquickprofileradapter.cpp b/src/plugins/qmltooling/qmldbg_quickprofiler/qquickprofileradapter.cpp index 2c152e4cd5..79a1c82411 100644 --- a/src/plugins/qmltooling/qmldbg_quickprofiler/qquickprofileradapter.cpp +++ b/src/plugins/qmltooling/qmldbg_quickprofiler/qquickprofileradapter.cpp @@ -152,10 +152,8 @@ static void qQuickProfilerDataToByteArrays(const QQuickProfilerData &data, } } -qint64 QQuickProfilerAdapter::sendMessages(qint64 until, QList<QByteArray> &messages, - bool trackLocations) +qint64 QQuickProfilerAdapter::sendMessages(qint64 until, QList<QByteArray> &messages) { - Q_UNUSED(trackLocations); while (next < m_data.size()) { if (m_data[next].time <= until && messages.length() <= s_numMessagesPerBatch) qQuickProfilerDataToByteArrays(m_data[next++], messages); diff --git a/src/plugins/qmltooling/qmldbg_quickprofiler/qquickprofileradapter.h b/src/plugins/qmltooling/qmldbg_quickprofiler/qquickprofileradapter.h index 1ad020afd6..1f3467c1d0 100644 --- a/src/plugins/qmltooling/qmldbg_quickprofiler/qquickprofileradapter.h +++ b/src/plugins/qmltooling/qmldbg_quickprofiler/qquickprofileradapter.h @@ -61,7 +61,7 @@ class QQuickProfilerAdapter : public QQmlAbstractProfilerAdapter { public: QQuickProfilerAdapter(QObject *parent = 0); ~QQuickProfilerAdapter(); - qint64 sendMessages(qint64 until, QList<QByteArray> &messages, bool trackLocations) override; + qint64 sendMessages(qint64 until, QList<QByteArray> &messages) override; void receiveData(const QVector<QQuickProfilerData> &new_data); private: diff --git a/src/plugins/qmltooling/qmldbg_server/qqmldebugserver.cpp b/src/plugins/qmltooling/qmldbg_server/qqmldebugserver.cpp index c1e86f0b3c..8293e88038 100644 --- a/src/plugins/qmltooling/qmldbg_server/qqmldebugserver.cpp +++ b/src/plugins/qmltooling/qmldbg_server/qqmldebugserver.cpp @@ -177,14 +177,13 @@ private: void changeServiceState(const QString &serviceName, QQmlDebugService::State state); void removeThread(); void receiveMessage(); - void invalidPacket(); + void protocolError(); QQmlDebugServerConnection *m_connection; QHash<QString, QQmlDebugService *> m_plugins; QStringList m_clientPlugins; bool m_gotHello; bool m_blockingMode; - bool m_clientSupportsMultiPackets; QHash<QJSEngine *, EngineCondition> m_engineConditions; @@ -277,8 +276,7 @@ static void cleanupOnShutdown() QQmlDebugServerImpl::QQmlDebugServerImpl() : m_connection(nullptr), m_gotHello(false), - m_blockingMode(false), - m_clientSupportsMultiPackets(false) + m_blockingMode(false) { static bool postRoutineAdded = false; if (!postRoutineAdded) { @@ -464,10 +462,9 @@ void QQmlDebugServerImpl::receiveMessage() s_dataStreamVersion = QDataStream::Qt_DefaultCompiledVersion; } + bool clientSupportsMultiPackets = false; if (!in.atEnd()) - in >> m_clientSupportsMultiPackets; - else - m_clientSupportsMultiPackets = false; + in >> clientSupportsMultiPackets; // Send the hello answer immediately, since it needs to arrive before // the plugins below start sending messages. @@ -475,13 +472,15 @@ void QQmlDebugServerImpl::receiveMessage() QQmlDebugPacket out; QStringList pluginNames; QList<float> pluginVersions; - const int count = m_plugins.count(); - pluginNames.reserve(count); - pluginVersions.reserve(count); - for (QHash<QString, QQmlDebugService *>::ConstIterator i = m_plugins.constBegin(); - i != m_plugins.constEnd(); ++i) { - pluginNames << i.key(); - pluginVersions << i.value()->version(); + if (clientSupportsMultiPackets) { // otherwise, disable all plugins + const int count = m_plugins.count(); + pluginNames.reserve(count); + pluginVersions.reserve(count); + for (QHash<QString, QQmlDebugService *>::ConstIterator i = m_plugins.constBegin(); + i != m_plugins.constEnd(); ++i) { + pluginNames << i.key(); + pluginVersions << i.value()->version(); + } } out << QString(QStringLiteral("QDeclarativeDebugClient")) << 0 << protocolVersion @@ -523,7 +522,7 @@ void QQmlDebugServerImpl::receiveMessage() } else { qWarning("QML Debugger: Invalid control message %d.", op); - invalidPacket(); + protocolError(); return; } @@ -701,16 +700,11 @@ void QQmlDebugServerImpl::sendMessage(const QString &name, const QByteArray &mes void QQmlDebugServerImpl::sendMessages(const QString &name, const QList<QByteArray> &messages) { if (canSendMessage(name)) { - if (m_clientSupportsMultiPackets) { - QQmlDebugPacket out; - out << name; - for (const QByteArray &message : messages) - out << message; - m_protocol->send(out.data()); - } else { - for (const QByteArray &message : messages) - doSendMessage(name, message); - } + QQmlDebugPacket out; + out << name; + for (const QByteArray &message : messages) + out << message; + m_protocol->send(out.data()); m_connection->flush(); } } @@ -743,16 +737,16 @@ void QQmlDebugServerImpl::setDevice(QIODevice *socket) m_protocol = new QPacketProtocol(socket, this); QObject::connect(m_protocol, &QPacketProtocol::readyRead, this, &QQmlDebugServerImpl::receiveMessage); - QObject::connect(m_protocol, &QPacketProtocol::invalidPacket, - this, &QQmlDebugServerImpl::invalidPacket); + QObject::connect(m_protocol, &QPacketProtocol::error, + this, &QQmlDebugServerImpl::protocolError); if (blockingMode()) m_protocol->waitForReadyRead(-1); } -void QQmlDebugServerImpl::invalidPacket() +void QQmlDebugServerImpl::protocolError() { - qWarning("QML Debugger: Received a corrupted packet! Giving up ..."); + qWarning("QML Debugger: A protocol error has occurred! Giving up ..."); m_connection->disconnect(); // protocol might still be processing packages at this point m_protocol->deleteLater(); diff --git a/src/plugins/qmltooling/qmltooling.pro b/src/plugins/qmltooling/qmltooling.pro index 119415372b..fd4a3eac65 100644 --- a/src/plugins/qmltooling/qmltooling.pro +++ b/src/plugins/qmltooling/qmltooling.pro @@ -35,7 +35,10 @@ qmldbg_nativedebugger.depends = packetprotocol qtHaveModule(quick) { SUBDIRS += \ qmldbg_inspector \ - qmldbg_quickprofiler + qmldbg_quickprofiler \ + qmldbg_preview + qmldbg_inspector.depends = packetprotocol qmldbg_quickprofiler.depends = packetprotocol + qmldbg_preview.depends = packetprotocol } diff --git a/src/plugins/scenegraph/openvg/qsgopenvginternalrectanglenode.cpp b/src/plugins/scenegraph/openvg/qsgopenvginternalrectanglenode.cpp index 0bd51cbf46..d728686248 100644 --- a/src/plugins/scenegraph/openvg/qsgopenvginternalrectanglenode.cpp +++ b/src/plugins/scenegraph/openvg/qsgopenvginternalrectanglenode.cpp @@ -176,6 +176,12 @@ void QSGOpenVGInternalRectangleNode::setGradientStops(const QGradientStops &stop m_fillDirty = true; } +void QSGOpenVGInternalRectangleNode::setGradientVertical(bool vertical) +{ + m_vertical = vertical; + m_fillDirty = true; +} + void QSGOpenVGInternalRectangleNode::setRadius(qreal radius) { m_radius = radius; @@ -242,13 +248,13 @@ void QSGOpenVGInternalRectangleNode::render() } else { // Linear Gradient vgSetParameteri(m_rectanglePaint, VG_PAINT_TYPE, VG_PAINT_TYPE_LINEAR_GRADIENT); - const VGfloat verticalLinearGradient[] = { - 0.0f, + const VGfloat linearGradient[] = { 0.0f, 0.0f, - static_cast<VGfloat>(m_rect.height()) + m_vertical ? 0.0f : static_cast<VGfloat>(m_rect.width()), + m_vertical ? static_cast<VGfloat>(m_rect.height()) : 0.0f }; - vgSetParameterfv(m_rectanglePaint, VG_PAINT_LINEAR_GRADIENT, 4, verticalLinearGradient); + vgSetParameterfv(m_rectanglePaint, VG_PAINT_LINEAR_GRADIENT, 4, linearGradient); vgSetParameteri(m_rectanglePaint, VG_PAINT_COLOR_RAMP_SPREAD_MODE, VG_COLOR_RAMP_SPREAD_PAD); vgSetParameteri(m_rectanglePaint, VG_PAINT_COLOR_RAMP_PREMULTIPLIED, false); diff --git a/src/plugins/scenegraph/openvg/qsgopenvginternalrectanglenode.h b/src/plugins/scenegraph/openvg/qsgopenvginternalrectanglenode.h index e8d25c94f8..86d2c3318c 100644 --- a/src/plugins/scenegraph/openvg/qsgopenvginternalrectanglenode.h +++ b/src/plugins/scenegraph/openvg/qsgopenvginternalrectanglenode.h @@ -59,6 +59,7 @@ public: void setPenColor(const QColor &color) override; void setPenWidth(qreal width) override; void setGradientStops(const QGradientStops &stops) override; + void setGradientVertical(bool vertical) override; void setRadius(qreal radius) override; void setAligned(bool aligned) override; void update() override; @@ -85,6 +86,7 @@ private: qreal m_penWidth = 0.0; qreal m_radius = 0.0; bool m_aligned = false; + bool m_vertical = true; QGradientStops m_gradientStops; VGPath m_rectanglePath; |