1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtQml module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "globalinspector.h"
41#include "highlight.h"
42#include "inspecttool.h"
43
44#include <private/qqmldebugserviceinterfaces_p.h>
45#include <private/qabstractanimation_p.h>
46#include <private/qqmlcomponent_p.h>
47#include <private/qqmldebugconnector_p.h>
48#include <private/qversionedpacket_p.h>
49
50#include <QtGui/qwindow.h>
51#include <QtCore/qregularexpression.h>
52
53//INSPECTOR SERVICE PROTOCOL
54// <HEADER><COMMAND><DATA>
55// <HEADER> : <type{request, response, event}><requestId/eventId>[<response_success_bool>]
56// <COMMAND> : {"enable", "disable", "select", "setAnimationSpeed",
57// "showAppOnTop", "createObject", "destroyObject", "moveObject"}
58// <DATA> : select: <debugIds_int_list>
59// setAnimationSpeed: <speed_real>
60// showAppOnTop: <set_bool>
61// createObject: <qml_string><parentId_int><imports_string_list><filename_string>
62// destroyObject: <debugId_int>
63// moveObject: <debugId_int><newParentId_int>
64// Response for "destroyObject" carries the <debugId_int> of the destroyed object.
65
66QT_BEGIN_NAMESPACE
67
68using QQmlDebugPacket = QVersionedPacket<QQmlDebugConnector>;
69
70const char REQUEST[] = "request";
71const char RESPONSE[] = "response";
72const char EVENT[] = "event";
73const char ENABLE[] = "enable";
74const char DISABLE[] = "disable";
75const char SELECT[] = "select";
76const char SET_ANIMATION_SPEED[] = "setAnimationSpeed";
77const char SHOW_APP_ON_TOP[] = "showAppOnTop";
78const char CREATE_OBJECT[] = "createObject";
79const char DESTROY_OBJECT[] = "destroyObject";
80const char MOVE_OBJECT[] = "moveObject";
81
82namespace QmlJSDebugger {
83
84void GlobalInspector::removeFromSelectedItems(QObject *object)
85{
86 if (QQuickItem *item = qobject_cast<QQuickItem*>(object)) {
87 if (m_selectedItems.removeOne(t: item))
88 delete m_highlightItems.take(key: item);
89 }
90}
91
92void GlobalInspector::setSelectedItems(const QList<QQuickItem *> &items)
93{
94 if (!syncSelectedItems(items))
95 return;
96
97 QList<QObject*> objectList;
98 objectList.reserve(size: items.count());
99 for (QQuickItem *item : items)
100 objectList << item;
101
102 sendCurrentObjects(objects: objectList);
103}
104
105void GlobalInspector::showSelectedItemName(QQuickItem *item, const QPointF &point)
106{
107 SelectionHighlight *highlightItem = m_highlightItems.value(key: item, defaultValue: 0);
108 if (highlightItem)
109 highlightItem->showName(displayPoint: point);
110}
111
112void GlobalInspector::sendCurrentObjects(const QList<QObject*> &objects)
113{
114 QQmlDebugPacket ds;
115
116 ds << QByteArray(EVENT) << m_eventId++ << QByteArray(SELECT);
117
118 QList<int> debugIds;
119 debugIds.reserve(size: objects.count());
120 for (QObject *object : objects)
121 debugIds << QQmlDebugService::idForObject(object);
122 ds << debugIds;
123
124 emit messageToClient(name: QQmlInspectorService::s_key, data: ds.data());
125}
126
127static bool reparentQmlObject(QObject *object, QObject *newParent)
128{
129 if (!newParent)
130 return false;
131
132 object->setParent(newParent);
133 QQuickItem *newParentItem = qobject_cast<QQuickItem*>(object: newParent);
134 QQuickItem *item = qobject_cast<QQuickItem*>(object);
135 if (newParentItem && item)
136 item->setParentItem(newParentItem);
137 return true;
138}
139
140class ObjectCreator : public QObject
141{
142 Q_OBJECT
143public:
144 ObjectCreator(int requestId, QQmlEngine *engine, QObject *parent) :
145 QObject(parent), m_component(engine), m_requestId(requestId)
146 {
147 connect(sender: &m_component, signal: &QQmlComponent::statusChanged, receiver: this, slot: &ObjectCreator::tryCreateObject);
148 }
149
150 void run(const QByteArray &qml, const QUrl &filename)
151 {
152 m_component.setData(qml, baseUrl: filename);
153 }
154
155 void tryCreateObject(QQmlComponent::Status status)
156 {
157 switch (status) {
158 case QQmlComponent::Error:
159 emit result(requestId: m_requestId, success: false);
160 delete this;
161 return;
162 case QQmlComponent::Ready: {
163 // Stuff might have changed. We have to lookup the parentContext again.
164 QQmlContext *parentContext = QQmlEngine::contextForObject(parent());
165 if (!parentContext) {
166 emit result(requestId: m_requestId, success: false);
167 } else {
168 QObject *newObject = m_component.create(context: parentContext);
169 if (newObject && reparentQmlObject(object: newObject, newParent: parent()))
170 emit result(requestId: m_requestId, success: true);
171 else
172 emit result(requestId: m_requestId, success: false);
173 }
174 deleteLater(); // The component might send more signals
175 return;
176 }
177 default:
178 break;
179 }
180 }
181
182signals:
183 void result(int requestId, bool success);
184
185private:
186 QQmlComponent m_component;
187 int m_requestId;
188};
189
190bool GlobalInspector::createQmlObject(int requestId, const QString &qml, QObject *parent,
191 const QStringList &importList, const QString &filename)
192{
193 if (!parent)
194 return false;
195
196 QQmlContext *parentContext = QQmlEngine::contextForObject(parent);
197 if (!parentContext)
198 return false;
199
200 QString imports;
201 for (const QString &s : importList)
202 imports += s + QLatin1Char('\n');
203
204 ObjectCreator *objectCreator = new ObjectCreator(requestId, parentContext->engine(), parent);
205 connect(sender: objectCreator, signal: &ObjectCreator::result, receiver: this, slot: &GlobalInspector::sendResult);
206 objectCreator->run(qml: (imports + qml).toUtf8(), filename: QUrl::fromLocalFile(localfile: filename));
207 return true;
208}
209
210void GlobalInspector::addWindow(QQuickWindow *window)
211{
212 m_windowInspectors.append(t: new QQuickWindowInspector(window, this));
213}
214
215void GlobalInspector::removeWindow(QQuickWindow *window)
216{
217 for (QList<QmlJSDebugger::QQuickWindowInspector *>::Iterator i = m_windowInspectors.begin();
218 i != m_windowInspectors.end();) {
219 if ((*i)->quickWindow() == window) {
220 delete *i;
221 i = m_windowInspectors.erase(pos: i);
222 } else {
223 ++i;
224 }
225 }
226}
227
228void GlobalInspector::setParentWindow(QQuickWindow *window, QWindow *parentWindow)
229{
230 for (QmlJSDebugger::QQuickWindowInspector *inspector : qAsConst(t&: m_windowInspectors)) {
231 if (inspector->quickWindow() == window)
232 inspector->setParentWindow(parentWindow);
233 }
234}
235
236bool GlobalInspector::syncSelectedItems(const QList<QQuickItem *> &items)
237{
238 bool selectionChanged = false;
239
240 // Disconnect and remove items that are no longer selected
241 const auto selectedItemsCopy = m_selectedItems;
242 for (QQuickItem *item : selectedItemsCopy) {
243 if (items.contains(t: item))
244 continue;
245
246 selectionChanged = true;
247 item->disconnect(receiver: this);
248 m_selectedItems.removeOne(t: item);
249 delete m_highlightItems.take(key: item);
250 }
251
252 // Connect and add newly selected items
253 for (QQuickItem *item : items) {
254 if (m_selectedItems.contains(t: item))
255 continue;
256
257 selectionChanged = true;
258 connect(sender: item, signal: &QObject::destroyed, receiver: this, slot: &GlobalInspector::removeFromSelectedItems);
259 m_selectedItems.append(t: item);
260 for (QQuickWindowInspector *inspector : qAsConst(t&: m_windowInspectors)) {
261 if (inspector->isEnabled() && inspector->quickWindow() == item->window()) {
262 m_highlightItems.insert(key: item, value: new SelectionHighlight(titleForItem(item), item,
263 inspector->overlay()));
264 break;
265 }
266 }
267 }
268
269 return selectionChanged;
270}
271
272QString GlobalInspector::titleForItem(QQuickItem *item) const
273{
274 QString className = QLatin1String(item->metaObject()->className());
275 QString objectStringId = idStringForObject(obj: item);
276
277#if QT_CONFIG(regularexpression)
278 className.remove(re: QRegularExpression(QLatin1String("_QMLTYPE_\\d+")));
279 className.remove(re: QRegularExpression(QLatin1String("_QML_\\d+")));
280#endif
281 if (className.startsWith(s: QLatin1String("QQuick")))
282 className = className.mid(position: 6);
283
284 QString constructedName;
285
286 if (!objectStringId.isEmpty()) {
287 constructedName = objectStringId + QLatin1String(" (") + className + QLatin1Char(')');
288 } else if (!item->objectName().isEmpty()) {
289 constructedName = item->objectName() + QLatin1String(" (") + className + QLatin1Char(')');
290 } else {
291 constructedName = className;
292 }
293
294 return constructedName;
295}
296
297QString GlobalInspector::idStringForObject(QObject *obj) const
298{
299 QQmlContext *context = qmlContext(obj);
300 if (context) {
301 QQmlContextData *cdata = QQmlContextData::get(context);
302 if (cdata)
303 return cdata->findObjectId(obj);
304 }
305 return QString();
306}
307
308void GlobalInspector::processMessage(const QByteArray &message)
309{
310 bool success = true;
311 QQmlDebugPacket ds(message);
312
313 QByteArray type;
314 ds >> type;
315
316 int requestId = -1;
317 if (type == REQUEST) {
318 QByteArray command;
319 ds >> requestId >> command;
320
321 if (command == ENABLE) {
322 for (QQuickWindowInspector *inspector : qAsConst(t&: m_windowInspectors))
323 inspector->setEnabled(true);
324 success = !m_windowInspectors.isEmpty();
325 } else if (command == DISABLE) {
326 setSelectedItems(QList<QQuickItem*>());
327 for (QQuickWindowInspector *inspector : qAsConst(t&: m_windowInspectors))
328 inspector->setEnabled(false);
329 success = !m_windowInspectors.isEmpty();
330 } else if (command == SELECT) {
331 QList<int> debugIds;
332 ds >> debugIds;
333
334 QList<QQuickItem *> selectedObjects;
335 for (int debugId : qAsConst(t&: debugIds)) {
336 if (QQuickItem *obj =
337 qobject_cast<QQuickItem *>(object: QQmlDebugService::objectForId(id: debugId)))
338 selectedObjects << obj;
339 }
340 syncSelectedItems(items: selectedObjects);
341 } else if (command == SET_ANIMATION_SPEED) {
342 qreal speed;
343 ds >> speed;
344 QUnifiedTimer::instance()->setSlowModeEnabled(speed != 1.0);
345 QUnifiedTimer::instance()->setSlowdownFactor(speed);
346 } else if (command == SHOW_APP_ON_TOP) {
347 bool showOnTop;
348 ds >> showOnTop;
349 for (QmlJSDebugger::QQuickWindowInspector *inspector : qAsConst(t&: m_windowInspectors))
350 inspector->setShowAppOnTop(showOnTop);
351 success = !m_windowInspectors.isEmpty();
352 } else if (command == CREATE_OBJECT) {
353 QString qml;
354 int parentId;
355 QString filename;
356 QStringList imports;
357 ds >> qml >> parentId >> imports >> filename;
358 if (QObject *parent = QQmlDebugService::objectForId(id: parentId)) {
359 if (createQmlObject(requestId, qml, parent, importList: imports, filename))
360 return; // will callback for result
361 else {
362 success = false;
363 }
364 } else {
365 success = false;
366 }
367
368 } else if (command == DESTROY_OBJECT) {
369 int debugId;
370 ds >> debugId;
371 if (QObject *obj = QQmlDebugService::objectForId(id: debugId))
372 delete obj;
373 else
374 success = false;
375
376 } else if (command == MOVE_OBJECT) {
377 int debugId, newParent;
378 ds >> debugId >> newParent;
379 success = reparentQmlObject(object: QQmlDebugService::objectForId(id: debugId),
380 newParent: QQmlDebugService::objectForId(id: newParent));
381 } else {
382 qWarning() << "Warning: Not handling command:" << command;
383 success = false;
384 }
385 } else {
386 qWarning() << "Warning: Not handling type:" << type << REQUEST;
387 success = false;
388 }
389
390 sendResult(requestId, success);
391}
392
393void GlobalInspector::sendResult(int requestId, bool success)
394{
395 QQmlDebugPacket rs;
396 rs << QByteArray(RESPONSE) << requestId << success;
397 emit messageToClient(name: QQmlInspectorService::s_key, data: rs.data());
398}
399
400GlobalInspector::~GlobalInspector()
401{
402 // Everything else is parented
403 qDeleteAll(c: m_highlightItems);
404}
405
406}
407
408QT_END_NAMESPACE
409
410#include <globalinspector.moc>
411

source code of qtdeclarative/src/plugins/qmltooling/qmldbg_inspector/globalinspector.cpp