Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
globalinspector.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant
4
6#include "highlight.h"
7#include "inspecttool.h"
8
9#include <private/qqmldebugserviceinterfaces_p.h>
10#include <private/qabstractanimation_p.h>
11#include <private/qqmlcomponent_p.h>
12#include <private/qqmldebugconnector_p.h>
13#include <private/qversionedpacket_p.h>
14
15#include <QtGui/qwindow.h>
16#if QT_CONFIG(regularexpression)
17# include <QtCore/qregularexpression.h>
18#endif
19//INSPECTOR SERVICE PROTOCOL
20// <HEADER><COMMAND><DATA>
21// <HEADER> : <type{request, response, event}><requestId/eventId>[<response_success_bool>]
22// <COMMAND> : {"enable", "disable", "select", "setAnimationSpeed",
23// "showAppOnTop", "createObject", "destroyObject", "moveObject"}
24// <DATA> : select: <debugIds_int_list>
25// setAnimationSpeed: <speed_real>
26// showAppOnTop: <set_bool>
27// createObject: <qml_string><parentId_int><imports_string_list><filename_string>
28// destroyObject: <debugId_int>
29// moveObject: <debugId_int><newParentId_int>
30// Response for "destroyObject" carries the <debugId_int> of the destroyed object.
31
33
34using QQmlDebugPacket = QVersionedPacket<QQmlDebugConnector>;
35
36const char REQUEST[] = "request";
37const char RESPONSE[] = "response";
38const char EVENT[] = "event";
39const char ENABLE[] = "enable";
40const char DISABLE[] = "disable";
41const char SELECT[] = "select";
42const char SET_ANIMATION_SPEED[] = "setAnimationSpeed";
43const char SHOW_APP_ON_TOP[] = "showAppOnTop";
44const char CREATE_OBJECT[] = "createObject";
45const char DESTROY_OBJECT[] = "destroyObject";
46const char MOVE_OBJECT[] = "moveObject";
47
48namespace QmlJSDebugger {
49
50void GlobalInspector::removeFromSelectedItems(QObject *object)
51{
52 if (QQuickItem *item = qobject_cast<QQuickItem*>(object)) {
53 if (m_selectedItems.removeOne(item))
54 delete m_highlightItems.take(item);
55 }
56}
57
58void GlobalInspector::setSelectedItems(const QList<QQuickItem *> &items)
59{
60 if (!syncSelectedItems(items))
61 return;
62
63 QList<QObject*> objectList;
64 objectList.reserve(items.size());
65 for (QQuickItem *item : items)
66 objectList << item;
67
68 sendCurrentObjects(objectList);
69}
70
71void GlobalInspector::showSelectedItemName(QQuickItem *item, const QPointF &point)
72{
73 SelectionHighlight *highlightItem = m_highlightItems.value(item, 0);
74 if (highlightItem)
75 highlightItem->showName(point);
76}
77
78void GlobalInspector::sendCurrentObjects(const QList<QObject*> &objects)
79{
80 QQmlDebugPacket ds;
81
82 ds << QByteArray(EVENT) << m_eventId++ << QByteArray(SELECT);
83
84 QList<int> debugIds;
85 debugIds.reserve(objects.size());
86 for (QObject *object : objects)
87 debugIds << QQmlDebugService::idForObject(object);
88 ds << debugIds;
89
90 emit messageToClient(QQmlInspectorService::s_key, ds.data());
91}
92
93static bool reparentQmlObject(QObject *object, QObject *newParent)
94{
95 if (!newParent)
96 return false;
97
98 object->setParent(newParent);
99 QQuickItem *newParentItem = qobject_cast<QQuickItem*>(newParent);
100 QQuickItem *item = qobject_cast<QQuickItem*>(object);
101 if (newParentItem && item)
102 item->setParentItem(newParentItem);
103 return true;
104}
105
106class ObjectCreator : public QObject
107{
109public:
115
116 void run(const QByteArray &qml, const QUrl &filename)
117 {
119 }
120
122 {
123 switch (status) {
124 case QQmlComponent::Error:
125 emit result(m_requestId, false);
126 delete this;
127 return;
128 case QQmlComponent::Ready: {
129 // Stuff might have changed. We have to lookup the parentContext again.
131 if (!parentContext) {
132 emit result(m_requestId, false);
133 } else {
136 emit result(m_requestId, true);
137 else
138 emit result(m_requestId, false);
139 }
140 deleteLater(); // The component might send more signals
141 return;
142 }
143 default:
144 break;
145 }
146 }
147
148signals:
149 void result(int requestId, bool success);
150
151private:
152 QQmlComponent m_component;
153 int m_requestId;
154};
155
156bool GlobalInspector::createQmlObject(int requestId, const QString &qml, QObject *parent,
157 const QStringList &importList, const QString &filename)
158{
159 if (!parent)
160 return false;
161
162 QQmlContext *parentContext = QQmlEngine::contextForObject(parent);
163 if (!parentContext)
164 return false;
165
166 QString imports;
167 for (const QString &s : importList)
168 imports += s + QLatin1Char('\n');
169
170 ObjectCreator *objectCreator = new ObjectCreator(requestId, parentContext->engine(), parent);
171 connect(objectCreator, &ObjectCreator::result, this, &GlobalInspector::sendResult);
172 objectCreator->run((imports + qml).toUtf8(), QUrl::fromLocalFile(filename));
173 return true;
174}
175
176void GlobalInspector::addWindow(QQuickWindow *window)
177{
178 m_windowInspectors.append(new QQuickWindowInspector(window, this));
179}
180
181void GlobalInspector::removeWindow(QQuickWindow *window)
182{
183 for (QList<QmlJSDebugger::QQuickWindowInspector *>::Iterator i = m_windowInspectors.begin();
184 i != m_windowInspectors.end();) {
185 if ((*i)->quickWindow() == window) {
186 delete *i;
187 i = m_windowInspectors.erase(i);
188 } else {
189 ++i;
190 }
191 }
192}
193
194void GlobalInspector::setParentWindow(QQuickWindow *window, QWindow *parentWindow)
195{
196 for (QmlJSDebugger::QQuickWindowInspector *inspector : std::as_const(m_windowInspectors)) {
197 if (inspector->quickWindow() == window)
198 inspector->setParentWindow(parentWindow);
199 }
200}
201
202bool GlobalInspector::syncSelectedItems(const QList<QQuickItem *> &items)
203{
204 bool selectionChanged = false;
205
206 // Disconnect and remove items that are no longer selected
207 const auto selectedItemsCopy = m_selectedItems;
208 for (QQuickItem *item : selectedItemsCopy) {
209 if (items.contains(item))
210 continue;
211
212 selectionChanged = true;
213 item->disconnect(this);
214 m_selectedItems.removeOne(item);
215 delete m_highlightItems.take(item);
216 }
217
218 // Connect and add newly selected items
219 for (QQuickItem *item : items) {
220 if (m_selectedItems.contains(item))
221 continue;
222
223 selectionChanged = true;
224 connect(item, &QObject::destroyed, this, &GlobalInspector::removeFromSelectedItems);
225 m_selectedItems.append(item);
226 for (QQuickWindowInspector *inspector : std::as_const(m_windowInspectors)) {
227 if (inspector->isEnabled() && inspector->quickWindow() == item->window()) {
228 m_highlightItems.insert(item, new SelectionHighlight(titleForItem(item), item,
229 inspector->overlay()));
230 break;
231 }
232 }
233 }
234
235 return selectionChanged;
236}
237
238QString GlobalInspector::titleForItem(QQuickItem *item) const
239{
240 QString className = QLatin1String(item->metaObject()->className());
241 QString objectStringId = idStringForObject(item);
242
243#if QT_CONFIG(regularexpression)
244 className.remove(QRegularExpression(QLatin1String("_QMLTYPE_\\d+")));
245 className.remove(QRegularExpression(QLatin1String("_QML_\\d+")));
246#endif
247 if (className.startsWith(QLatin1String("QQuick")))
248 className = className.mid(6);
249
250 QString constructedName;
251
252 if (!objectStringId.isEmpty()) {
253 constructedName = objectStringId + QLatin1String(" (") + className + QLatin1Char(')');
254 } else if (!item->objectName().isEmpty()) {
255 constructedName = item->objectName() + QLatin1String(" (") + className + QLatin1Char(')');
256 } else {
257 constructedName = className;
258 }
259
260 return constructedName;
261}
262
263QString GlobalInspector::idStringForObject(QObject *obj) const
264{
265 QQmlContext *context = qmlContext(obj);
266 if (context) {
267 QQmlRefPointer<QQmlContextData> cdata = QQmlContextData::get(context);
268 if (cdata)
269 return cdata->findObjectId(obj);
270 }
271 return QString();
272}
273
274void GlobalInspector::processMessage(const QByteArray &message)
275{
276 bool success = true;
277 QQmlDebugPacket ds(message);
278
279 QByteArray type;
280 ds >> type;
281
282 int requestId = -1;
283 if (type == REQUEST) {
284 QByteArray command;
285 ds >> requestId >> command;
286
287 if (command == ENABLE) {
288 for (QQuickWindowInspector *inspector : std::as_const(m_windowInspectors))
289 inspector->setEnabled(true);
290 success = !m_windowInspectors.isEmpty();
291 } else if (command == DISABLE) {
292 setSelectedItems(QList<QQuickItem*>());
293 for (QQuickWindowInspector *inspector : std::as_const(m_windowInspectors))
294 inspector->setEnabled(false);
295 success = !m_windowInspectors.isEmpty();
296 } else if (command == SELECT) {
297 QList<int> debugIds;
298 ds >> debugIds;
299
300 QList<QQuickItem *> selectedObjects;
301 for (int debugId : std::as_const(debugIds)) {
302 if (QQuickItem *obj =
303 qobject_cast<QQuickItem *>(QQmlDebugService::objectForId(debugId)))
304 selectedObjects << obj;
305 }
306 syncSelectedItems(selectedObjects);
307 } else if (command == SET_ANIMATION_SPEED) {
308 qreal speed;
309 ds >> speed;
310 QUnifiedTimer::instance()->setSpeedModifier(1 / speed);
311 } else if (command == SHOW_APP_ON_TOP) {
312 bool showOnTop;
313 ds >> showOnTop;
314 for (QmlJSDebugger::QQuickWindowInspector *inspector : std::as_const(m_windowInspectors))
315 inspector->setShowAppOnTop(showOnTop);
316 success = !m_windowInspectors.isEmpty();
317 } else if (command == CREATE_OBJECT) {
318 QString qml;
319 int parentId;
320 QString filename;
321 QStringList imports;
322 ds >> qml >> parentId >> imports >> filename;
323 if (QObject *parent = QQmlDebugService::objectForId(parentId)) {
324 if (createQmlObject(requestId, qml, parent, imports, filename))
325 return; // will callback for result
326 else {
327 success = false;
328 }
329 } else {
330 success = false;
331 }
332
333 } else if (command == DESTROY_OBJECT) {
334 int debugId;
335 ds >> debugId;
336 if (QObject *obj = QQmlDebugService::objectForId(debugId))
337 delete obj;
338 else
339 success = false;
340
341 } else if (command == MOVE_OBJECT) {
342 int debugId, newParent;
343 ds >> debugId >> newParent;
344 success = reparentQmlObject(QQmlDebugService::objectForId(debugId),
345 QQmlDebugService::objectForId(newParent));
346 } else {
347 qWarning() << "Warning: Not handling command:" << command;
348 success = false;
349 }
350 } else {
351 qWarning() << "Warning: Not handling type:" << type << REQUEST;
352 success = false;
353 }
354
355 sendResult(requestId, success);
356}
357
358void GlobalInspector::sendResult(int requestId, bool success)
359{
360 QQmlDebugPacket rs;
361 rs << QByteArray(RESPONSE) << requestId << success;
362 emit messageToClient(QQmlInspectorService::s_key, rs.data());
363}
364
365GlobalInspector::~GlobalInspector()
366{
367 // Everything else is parented
368 qDeleteAll(m_highlightItems);
369}
370
371}
372
373QT_END_NAMESPACE
374
375#include "moc_globalinspector.cpp"
376
377#include <globalinspector.moc>
void processMessage(const QByteArray &message)
void setParentWindow(QQuickWindow *window, QWindow *parentWindow)
void removeWindow(QQuickWindow *window)
void showSelectedItemName(QQuickItem *item, const QPointF &point)
void setSelectedItems(const QList< QQuickItem * > &items)
void addWindow(QQuickWindow *window)
const char RESPONSE[]
const char SHOW_APP_ON_TOP[]
const char MOVE_OBJECT[]
const char SET_ANIMATION_SPEED[]
const char SELECT[]
const char DISABLE[]
const char ENABLE[]
const char REQUEST[]
const char CREATE_OBJECT[]
const char EVENT[]
const char DESTROY_OBJECT[]
Combined button and popup list for selecting options.
static bool reparentQmlObject(QObject *object, QObject *newParent)