blob: 012730902bea247ca8e2b0b060811befb36bab37 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtQml module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and 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 Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** 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-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "globalinspector.h"
#include "highlight.h"
#include "inspecttool.h"
#include <private/qqmldebugserviceinterfaces_p.h>
#include <private/qabstractanimation_p.h>
#include <private/qqmlcomponent_p.h>
#include <private/qqmldebugconnector_p.h>
#include <private/qversionedpacket_p.h>
#include <QtGui/qwindow.h>
#include <QtCore/qregularexpression.h>
//INSPECTOR SERVICE PROTOCOL
// <HEADER><COMMAND><DATA>
// <HEADER> : <type{request, response, event}><requestId/eventId>[<response_success_bool>]
// <COMMAND> : {"enable", "disable", "select", "setAnimationSpeed",
// "showAppOnTop", "createObject", "destroyObject", "moveObject"}
// <DATA> : select: <debugIds_int_list>
// setAnimationSpeed: <speed_real>
// showAppOnTop: <set_bool>
// createObject: <qml_string><parentId_int><imports_string_list><filename_string>
// destroyObject: <debugId_int>
// moveObject: <debugId_int><newParentId_int>
// Response for "destroyObject" carries the <debugId_int> of the destroyed object.
QT_BEGIN_NAMESPACE
using QQmlDebugPacket = QVersionedPacket<QQmlDebugConnector>;
const char REQUEST[] = "request";
const char RESPONSE[] = "response";
const char EVENT[] = "event";
const char ENABLE[] = "enable";
const char DISABLE[] = "disable";
const char SELECT[] = "select";
const char SET_ANIMATION_SPEED[] = "setAnimationSpeed";
const char SHOW_APP_ON_TOP[] = "showAppOnTop";
const char CREATE_OBJECT[] = "createObject";
const char DESTROY_OBJECT[] = "destroyObject";
const char MOVE_OBJECT[] = "moveObject";
namespace QmlJSDebugger {
void GlobalInspector::removeFromSelectedItems(QObject *object)
{
if (QQuickItem *item = qobject_cast<QQuickItem*>(object)) {
if (m_selectedItems.removeOne(item))
delete m_highlightItems.take(item);
}
}
void GlobalInspector::setSelectedItems(const QList<QQuickItem *> &items)
{
if (!syncSelectedItems(items))
return;
QList<QObject*> objectList;
objectList.reserve(items.count());
for (QQuickItem *item : items)
objectList << item;
sendCurrentObjects(objectList);
}
void GlobalInspector::showSelectedItemName(QQuickItem *item, const QPointF &point)
{
SelectionHighlight *highlightItem = m_highlightItems.value(item, 0);
if (highlightItem)
highlightItem->showName(point);
}
void GlobalInspector::sendCurrentObjects(const QList<QObject*> &objects)
{
QQmlDebugPacket ds;
ds << QByteArray(EVENT) << m_eventId++ << QByteArray(SELECT);
QList<int> debugIds;
debugIds.reserve(objects.count());
for (QObject *object : objects)
debugIds << QQmlDebugService::idForObject(object);
ds << debugIds;
emit messageToClient(QQmlInspectorService::s_key, ds.data());
}
static bool reparentQmlObject(QObject *object, QObject *newParent)
{
if (!newParent)
return false;
object->setParent(newParent);
QQuickItem *newParentItem = qobject_cast<QQuickItem*>(newParent);
QQuickItem *item = qobject_cast<QQuickItem*>(object);
if (newParentItem && item)
item->setParentItem(newParentItem);
return true;
}
class ObjectCreator : public QObject
{
Q_OBJECT
public:
ObjectCreator(int requestId, QQmlEngine *engine, QObject *parent) :
QObject(parent), m_component(engine), m_requestId(requestId)
{
connect(&m_component, &QQmlComponent::statusChanged, this, &ObjectCreator::tryCreateObject);
}
void run(const QByteArray &qml, const QUrl &filename)
{
m_component.setData(qml, filename);
}
void tryCreateObject(QQmlComponent::Status status)
{
switch (status) {
case QQmlComponent::Error:
emit result(m_requestId, false);
delete this;
return;
case QQmlComponent::Ready: {
// Stuff might have changed. We have to lookup the parentContext again.
QQmlContext *parentContext = QQmlEngine::contextForObject(parent());
if (!parentContext) {
emit result(m_requestId, false);
} else {
QObject *newObject = m_component.create(parentContext);
if (newObject && reparentQmlObject(newObject, parent()))
emit result(m_requestId, true);
else
emit result(m_requestId, false);
}
deleteLater(); // The component might send more signals
return;
}
default:
break;
}
}
signals:
void result(int requestId, bool success);
private:
QQmlComponent m_component;
int m_requestId;
};
bool GlobalInspector::createQmlObject(int requestId, const QString &qml, QObject *parent,
const QStringList &importList, const QString &filename)
{
if (!parent)
return false;
QQmlContext *parentContext = QQmlEngine::contextForObject(parent);
if (!parentContext)
return false;
QString imports;
for (const QString &s : importList)
imports += s + QLatin1Char('\n');
ObjectCreator *objectCreator = new ObjectCreator(requestId, parentContext->engine(), parent);
connect(objectCreator, &ObjectCreator::result, this, &GlobalInspector::sendResult);
objectCreator->run((imports + qml).toUtf8(), QUrl::fromLocalFile(filename));
return true;
}
void GlobalInspector::addWindow(QQuickWindow *window)
{
m_windowInspectors.append(new QQuickWindowInspector(window, this));
}
void GlobalInspector::removeWindow(QQuickWindow *window)
{
for (QList<QmlJSDebugger::QQuickWindowInspector *>::Iterator i = m_windowInspectors.begin();
i != m_windowInspectors.end();) {
if ((*i)->quickWindow() == window) {
delete *i;
i = m_windowInspectors.erase(i);
} else {
++i;
}
}
}
void GlobalInspector::setParentWindow(QQuickWindow *window, QWindow *parentWindow)
{
for (QmlJSDebugger::QQuickWindowInspector *inspector : qAsConst(m_windowInspectors)) {
if (inspector->quickWindow() == window)
inspector->setParentWindow(parentWindow);
}
}
bool GlobalInspector::syncSelectedItems(const QList<QQuickItem *> &items)
{
bool selectionChanged = false;
// Disconnect and remove items that are no longer selected
const auto selectedItemsCopy = m_selectedItems;
for (QQuickItem *item : selectedItemsCopy) {
if (items.contains(item))
continue;
selectionChanged = true;
item->disconnect(this);
m_selectedItems.removeOne(item);
delete m_highlightItems.take(item);
}
// Connect and add newly selected items
for (QQuickItem *item : items) {
if (m_selectedItems.contains(item))
continue;
selectionChanged = true;
connect(item, &QObject::destroyed, this, &GlobalInspector::removeFromSelectedItems);
m_selectedItems.append(item);
for (QQuickWindowInspector *inspector : qAsConst(m_windowInspectors)) {
if (inspector->isEnabled() && inspector->quickWindow() == item->window()) {
m_highlightItems.insert(item, new SelectionHighlight(titleForItem(item), item,
inspector->overlay()));
break;
}
}
}
return selectionChanged;
}
QString GlobalInspector::titleForItem(QQuickItem *item) const
{
QString className = QLatin1String(item->metaObject()->className());
QString objectStringId = idStringForObject(item);
#if QT_CONFIG(regularexpression)
className.remove(QRegularExpression(QLatin1String("_QMLTYPE_\\d+")));
className.remove(QRegularExpression(QLatin1String("_QML_\\d+")));
#endif
if (className.startsWith(QLatin1String("QQuick")))
className = className.mid(6);
QString constructedName;
if (!objectStringId.isEmpty()) {
constructedName = objectStringId + QLatin1String(" (") + className + QLatin1Char(')');
} else if (!item->objectName().isEmpty()) {
constructedName = item->objectName() + QLatin1String(" (") + className + QLatin1Char(')');
} else {
constructedName = className;
}
return constructedName;
}
QString GlobalInspector::idStringForObject(QObject *obj) const
{
QQmlContext *context = qmlContext(obj);
if (context) {
QQmlContextData *cdata = QQmlContextData::get(context);
if (cdata)
return cdata->findObjectId(obj);
}
return QString();
}
void GlobalInspector::processMessage(const QByteArray &message)
{
bool success = true;
QQmlDebugPacket ds(message);
QByteArray type;
ds >> type;
int requestId = -1;
if (type == REQUEST) {
QByteArray command;
ds >> requestId >> command;
if (command == ENABLE) {
for (QQuickWindowInspector *inspector : qAsConst(m_windowInspectors))
inspector->setEnabled(true);
success = !m_windowInspectors.isEmpty();
} else if (command == DISABLE) {
setSelectedItems(QList<QQuickItem*>());
for (QQuickWindowInspector *inspector : qAsConst(m_windowInspectors))
inspector->setEnabled(false);
success = !m_windowInspectors.isEmpty();
} else if (command == SELECT) {
QList<int> debugIds;
ds >> debugIds;
QList<QQuickItem *> selectedObjects;
for (int debugId : qAsConst(debugIds)) {
if (QQuickItem *obj =
qobject_cast<QQuickItem *>(QQmlDebugService::objectForId(debugId)))
selectedObjects << obj;
}
syncSelectedItems(selectedObjects);
} else if (command == SET_ANIMATION_SPEED) {
qreal speed;
ds >> speed;
QUnifiedTimer::instance()->setSlowModeEnabled(speed != 1.0);
QUnifiedTimer::instance()->setSlowdownFactor(speed);
} else if (command == SHOW_APP_ON_TOP) {
bool showOnTop;
ds >> showOnTop;
for (QmlJSDebugger::QQuickWindowInspector *inspector : qAsConst(m_windowInspectors))
inspector->setShowAppOnTop(showOnTop);
success = !m_windowInspectors.isEmpty();
} else if (command == CREATE_OBJECT) {
QString qml;
int parentId;
QString filename;
QStringList imports;
ds >> qml >> parentId >> imports >> filename;
if (QObject *parent = QQmlDebugService::objectForId(parentId)) {
if (createQmlObject(requestId, qml, parent, imports, filename))
return; // will callback for result
else {
success = false;
}
} else {
success = false;
}
} else if (command == DESTROY_OBJECT) {
int debugId;
ds >> debugId;
if (QObject *obj = QQmlDebugService::objectForId(debugId))
delete obj;
else
success = false;
} else if (command == MOVE_OBJECT) {
int debugId, newParent;
ds >> debugId >> newParent;
success = reparentQmlObject(QQmlDebugService::objectForId(debugId),
QQmlDebugService::objectForId(newParent));
} else {
qWarning() << "Warning: Not handling command:" << command;
success = false;
}
} else {
qWarning() << "Warning: Not handling type:" << type << REQUEST;
success = false;
}
sendResult(requestId, success);
}
void GlobalInspector::sendResult(int requestId, bool success)
{
QQmlDebugPacket rs;
rs << QByteArray(RESPONSE) << requestId << success;
emit messageToClient(QQmlInspectorService::s_key, rs.data());
}
GlobalInspector::~GlobalInspector()
{
// Everything else is parented
qDeleteAll(m_highlightItems);
}
}
QT_END_NAMESPACE
#include <globalinspector.moc>