From ac6bb3170d86006832170377cd5f51ff809b4455 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Mon, 9 Jul 2018 10:26:49 +0200 Subject: Tooling: Add QML preview debug service Task-number: QDS-181 Change-Id: I02193afb84aa111792d8bebff3bdd9b410f9db5a Reviewed-by: Simon Hausmann --- .../qmltooling/qmldbg_preview/qmldbg_preview.pro | 29 ++ .../qmldbg_preview/qqmlpreviewblacklist.cpp | 202 ++++++++++ .../qmldbg_preview/qqmlpreviewblacklist.h | 89 +++++ .../qmldbg_preview/qqmlpreviewfileengine.cpp | 432 +++++++++++++++++++++ .../qmldbg_preview/qqmlpreviewfileengine.h | 125 ++++++ .../qmldbg_preview/qqmlpreviewfileloader.cpp | 181 +++++++++ .../qmldbg_preview/qqmlpreviewfileloader.h | 105 +++++ .../qmldbg_preview/qqmlpreviewhandler.cpp | 313 +++++++++++++++ .../qmltooling/qmldbg_preview/qqmlpreviewhandler.h | 92 +++++ .../qmldbg_preview/qqmlpreviewposition.cpp | 102 +++++ .../qmldbg_preview/qqmlpreviewposition.h | 76 ++++ .../qmldbg_preview/qqmlpreviewservice.cpp | 150 +++++++ .../qmltooling/qmldbg_preview/qqmlpreviewservice.h | 97 +++++ .../qmldbg_preview/qqmlpreviewservice.json | 3 + .../qmldbg_preview/qqmlpreviewservicefactory.cpp | 39 ++ .../qmldbg_preview/qqmlpreviewservicefactory.h | 58 +++ src/plugins/qmltooling/qmltooling.pro | 5 +- 17 files changed, 2097 insertions(+), 1 deletion(-) create mode 100644 src/plugins/qmltooling/qmldbg_preview/qmldbg_preview.pro create mode 100644 src/plugins/qmltooling/qmldbg_preview/qqmlpreviewblacklist.cpp create mode 100644 src/plugins/qmltooling/qmldbg_preview/qqmlpreviewblacklist.h create mode 100644 src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileengine.cpp create mode 100644 src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileengine.h create mode 100644 src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileloader.cpp create mode 100644 src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileloader.h create mode 100644 src/plugins/qmltooling/qmldbg_preview/qqmlpreviewhandler.cpp create mode 100644 src/plugins/qmltooling/qmldbg_preview/qqmlpreviewhandler.h create mode 100644 src/plugins/qmltooling/qmldbg_preview/qqmlpreviewposition.cpp create mode 100644 src/plugins/qmltooling/qmldbg_preview/qqmlpreviewposition.h create mode 100644 src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservice.cpp create mode 100644 src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservice.h create mode 100644 src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservice.json create mode 100644 src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservicefactory.cpp create mode 100644 src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservicefactory.h (limited to 'src/plugins') 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 &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 +#include +#include +#include + +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 &next = QHash(), + bool isLeaf = true); + + QString m_mine; + QHash 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 +#include +#include + +#include + +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(-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::max()) + return false; + m_contents.buffer().resize(static_cast(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 +#include +#include + +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 m_loader; + + mutable QBuffer m_contents; + mutable QStringList m_entries; + mutable QScopedPointer 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 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..6026d58746 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileloader.cpp @@ -0,0 +1,181 @@ +/**************************************************************************** +** +** 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 +#include + +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"); + + for (int loc = QLibraryInfo::PrefixPath; loc <= QLibraryInfo::TestsPath; ++loc) { + m_blacklist.blacklist(QLibraryInfo::location( + static_cast(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 +#include +#include +#include +#include +#include +#include +#include + +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 m_service; + + QString m_path; + QByteArray m_contents; + QStringList m_entries; + Result m_result; + + QQmlPreviewBlacklist m_blacklist; + QHash m_fileCache; + QHash 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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +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(obj)) { + m_lastPosition.initLastSavedWindowPosition(window); + } + } + if ((event->type() == QEvent::Move || event->type() == QEvent::Resize) && + qobject_cast(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 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(object)) { + m_currentWindow = qobject_cast(window); + for (QWindow *otherWindow : QGuiApplication::allWindows()) { + if (QQuickWindow *quickWindow = qobject_cast(otherWindow)) { + if (quickWindow == m_currentWindow) + continue; + quickWindow->setVisible(false); + quickWindow->setFlags(quickWindow->flags() & ~Qt::WindowStaysOnTopHint); + } + } + } else if (QQuickItem *item = qobject_cast(object)) { + m_currentWindow = nullptr; + for (QWindow *window : QGuiApplication::allWindows()) { + if (QQuickWindow *quickWindow = qobject_cast(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(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 +#include +#include +#include +#include + +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 m_dummyItem; + QList m_engines; + QVector> m_createdObjects; + QScopedPointer m_component; + QPointer 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 +#include +#include + +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 +#include +#include +#include +#include + +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 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..4e96fc62ef --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservice.cpp @@ -0,0 +1,150 @@ +/**************************************************************************** +** +** 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 +#include +#include +#include +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +const QString QQmlPreviewServiceImpl::s_key = QStringLiteral("QmlPreview"); +using QQmlDebugPacket = QVersionedPacket; + +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); + break; + } + case Directory: { + QString path; + QStringList entries; + packet >> path >> entries; + emit directory(path, entries); + break; + } + case Load: { + QUrl url; + packet >> 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(factor)); + break; + } + default: + forwardError(QString::fromLatin1("Invalid command: %1").arg(command)); + break; + } +} + +void QQmlPreviewServiceImpl::engineAboutToBeAdded(QJSEngine *engine) +{ + if (QQmlEngine *qmlEngine = qobject_cast(engine)) + m_handler.addEngine(qmlEngine); + emit attachedToEngine(engine); +} + +void QQmlPreviewServiceImpl::engineAboutToBeRemoved(QJSEngine *engine) +{ + if (QQmlEngine *qmlEngine = qobject_cast(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(Request) << file; + emit messageToClient(name(), packet.data()); +} + +void QQmlPreviewServiceImpl::forwardError(const QString &error) +{ + QQmlDebugPacket packet; + packet << static_cast(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..084d3a14b1 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservice.h @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** 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 + +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 m_fileEngine; + QScopedPointer m_loader; + QQmlPreviewHandler m_handler; +}; + +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 + +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/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 } -- cgit v1.2.3