Files
qt-creator/src/plugins/debugger/qml/qmlinspectoragent.cpp

944 lines
33 KiB
C++
Raw Normal View History

/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** 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.
**
****************************************************************************/
#include "qmlinspectoragent.h"
#include "qmlengine.h"
#include <debugger/debuggeractions.h>
#include <debugger/debuggercore.h>
#include <debugger/debuggerengine.h>
#include <debugger/debuggerruncontrol.h>
#include <debugger/watchhandler.h>
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/icore.h>
#include <coreplugin/idocument.h>
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/editormanager/documentmodel.h>
#include <qmldebug/declarativeenginedebugclient.h>
#include <qmldebug/declarativeenginedebugclientv2.h>
#include <qmldebug/declarativetoolsclient.h>
#include <qmldebug/qmldebugconstants.h>
#include <qmldebug/qmlenginedebugclient.h>
#include <qmldebug/qmltoolsclient.h>
#include <utils/fileutils.h>
#include <utils/qtcassert.h>
#include <utils/savedaction.h>
#include <QElapsedTimer>
#include <QFileInfo>
#include <QLoggingCategory>
using namespace QmlDebug;
using namespace QmlDebug::Constants;
namespace Debugger {
namespace Internal {
Q_LOGGING_CATEGORY(qmlInspectorLog, "qtc.dbg.qmlinspector")
/*!
* DebuggerAgent updates the watchhandler with the object tree data.
*/
QmlInspectorAgent::QmlInspectorAgent(QmlEngine *engine, QmlDebugConnection *connection)
: m_qmlEngine(engine)
, m_objectToSelect(WatchItem::InvalidId)
Debugger: Make most views per-engine instead of singletons This is a step towards properly supporting multiple debugger sessions side-by-side. The combined C++-and-QML engine has been removed, instead a combined setup creates now two individual engines, under a single DebuggerRunTool but mostly independent with no combined state machine. This requires a few more clicks in some cases, but makes it easier to direct e.g. interrupt requests to the interesting engine. Care has been taken to not change the UX of the single debugger session use case if possible. The fat debug button operates as-before in that case, i.e. switches to Interrupt if the single active runconfiguration runs in the debugger etc. Most views are made per-engine, running an engine creates a new Perspective, which is destroyed when the run control dies. The snapshot view remains global and becomes primary source of information on a "current engine" that receives all menu and otherwise global input. There is a new global "Breakpoint Preset" view containing all "static" breakpoint data. When an engine starts up it "claims" breakpoint it believes it can handle, but operates on a copy of the static data. The markers of the static version are suppressed as long as an engine controls a breakpoint (that inclusive all resolved locations), but are re-instatet once the engine quits. The old Breakpoint class that already contained this split per-instance was split into a new Breakpoint and a GlobalBreakpoint class, with a per-engine model for Breakpoints, and a singleton model containing GlobalBreakpoints. There is a new CppDebuggerEngine intermediate level serving as base for C++ (or, rather, "compiled") binary debugging, i.e. {Gdb,Lldb,Cdb}Engine, taking over bits of the current DebuggerEngine base that are not applicable to non-binary debuggers. Change-Id: I9994f4c188379b4aee0c4f379edd4759fbb0bd43 Reviewed-by: Christian Stenger <christian.stenger@qt.io> Reviewed-by: hjk <hjk@qt.io>
2018-07-31 12:30:48 +02:00
, m_toolsClient(nullptr)
, m_targetToSync(NoTarget)
, m_debugIdToSelect(WatchItem::InvalidId)
, m_currentSelectedDebugId(WatchItem::InvalidId)
, m_inspectorToolsContext("Debugger.QmlInspector")
, m_selectAction(new QAction(this))
, m_zoomAction(new QAction(this))
, m_showAppOnTopAction(action(ShowAppOnTop))
{
m_debugIdToIname.insert(WatchItem::InvalidId, "inspect");
connect(action(ShowQmlObjectTree),
&Utils::SavedAction::valueChanged, this, &QmlInspectorAgent::updateState);
connect(action(SortStructMembers), &Utils::SavedAction::valueChanged,
this, &QmlInspectorAgent::updateState);
m_delayQueryTimer.setSingleShot(true);
m_delayQueryTimer.setInterval(100);
connect(&m_delayQueryTimer, &QTimer::timeout,
this, &QmlInspectorAgent::queryEngineContext);
auto engineClient1 = new DeclarativeEngineDebugClient(connection);
connect(engineClient1, &BaseEngineDebugClient::newState,
this, &QmlInspectorAgent::clientStateChanged);
connect(engineClient1, &BaseEngineDebugClient::newState,
this, &QmlInspectorAgent::engineClientStateChanged);
auto engineClient2 = new QmlEngineDebugClient(connection);
connect(engineClient2, &BaseEngineDebugClient::newState,
this, &QmlInspectorAgent::clientStateChanged);
connect(engineClient2, &BaseEngineDebugClient::newState,
this, &QmlInspectorAgent::engineClientStateChanged);
auto engineClient3 = new DeclarativeEngineDebugClientV2(connection);
connect(engineClient3, &BaseEngineDebugClient::newState,
this, &QmlInspectorAgent::clientStateChanged);
connect(engineClient3, &BaseEngineDebugClient::newState,
this, &QmlInspectorAgent::engineClientStateChanged);
m_engineClients.insert(engineClient1->name(), engineClient1);
m_engineClients.insert(engineClient2->name(), engineClient2);
m_engineClients.insert(engineClient3->name(), engineClient3);
if (engineClient1->state() == QmlDebugClient::Enabled)
setActiveEngineClient(engineClient1);
if (engineClient2->state() == QmlDebugClient::Enabled)
setActiveEngineClient(engineClient2);
if (engineClient3->state() == QmlDebugClient::Enabled)
setActiveEngineClient(engineClient3);
auto toolsClient1 = new DeclarativeToolsClient(connection);
connect(toolsClient1, &BaseToolsClient::newState,
this, &QmlInspectorAgent::clientStateChanged);
connect(toolsClient1, &BaseToolsClient::newState,
this, &QmlInspectorAgent::toolsClientStateChanged);
auto toolsClient2 = new QmlToolsClient(connection);
connect(toolsClient2, &BaseToolsClient::newState,
this, &QmlInspectorAgent::clientStateChanged);
connect(toolsClient2, &BaseToolsClient::newState,
this, &QmlInspectorAgent::toolsClientStateChanged);
// toolbar
m_selectAction->setObjectName(QLatin1String("QML Select Action"));
m_zoomAction->setObjectName(QLatin1String("QML Zoom Action"));
m_selectAction->setCheckable(true);
m_zoomAction->setCheckable(true);
m_showAppOnTopAction->setCheckable(true);
m_selectAction->setEnabled(false);
m_zoomAction->setEnabled(false);
m_showAppOnTopAction->setEnabled(false);
connect(m_selectAction, &QAction::triggered,
this, &QmlInspectorAgent::onSelectActionTriggered);
connect(m_zoomAction, &QAction::triggered,
this, &QmlInspectorAgent::onZoomActionTriggered);
connect(m_showAppOnTopAction, &QAction::triggered,
this, &QmlInspectorAgent::onShowAppOnTopChanged);
}
quint32 QmlInspectorAgent::queryExpressionResult(int debugId,
const QString &expression)
{
if (!m_engineClient)
return 0;
qCDebug(qmlInspectorLog)
<< __FUNCTION__ << '(' << debugId << expression
<< m_engine.debugId() << ')';
return m_engineClient->queryExpressionResult(debugId, expression,
m_engine.debugId());
}
void QmlInspectorAgent::assignValue(const WatchItem *data,
const QString &expr, const QVariant &valueV)
{
qCDebug(qmlInspectorLog)
<< __FUNCTION__ << '(' << data->id << ')' << data->iname;
if (data->id != WatchItem::InvalidId) {
QString val(valueV.toString());
QString expression = QString("%1 = %2;").arg(expr).arg(val);
queryExpressionResult(data->id, expression);
}
}
static int parentIdForIname(const QString &iname)
{
// Extract the parent id
int lastIndex = iname.lastIndexOf('.');
int secondLastIndex = iname.lastIndexOf('.', lastIndex - 1);
int parentId = WatchItem::InvalidId;
if (secondLastIndex != WatchItem::InvalidId)
parentId = iname.mid(secondLastIndex + 1, lastIndex - secondLastIndex - 1).toInt();
return parentId;
}
void QmlInspectorAgent::updateWatchData(const WatchItem &data)
{
qCDebug(qmlInspectorLog) << __FUNCTION__ << '(' << data.id << ')';
if (data.id != WatchItem::InvalidId && !m_fetchDataIds.contains(data.id)) {
// objects
m_fetchDataIds << data.id;
fetchObject(data.id);
}
}
void QmlInspectorAgent::watchDataSelected(qint64 id)
{
qCDebug(qmlInspectorLog) << __FUNCTION__ << '(' << id << ')';
if (id != WatchItem::InvalidId) {
QTC_ASSERT(m_debugIdLocations.keys().contains(id), return);
jumpToObjectDefinitionInEditor(m_debugIdLocations.value(id), id);
if (m_toolsClient)
m_toolsClient->setObjectIdList({ObjectReference(static_cast<int>(id))});
}
}
bool QmlInspectorAgent::selectObjectInTree(int debugId)
{
qCDebug(qmlInspectorLog) << __FUNCTION__ << '(' << debugId << ')' << endl
<< " " << debugId << "already fetched? "
<< m_debugIdToIname.contains(debugId);
if (m_debugIdToIname.contains(debugId)) {
QString iname = m_debugIdToIname.value(debugId);
QTC_ASSERT(iname.startsWith("inspect."), qDebug() << iname);
qCDebug(qmlInspectorLog) << " selecting" << iname << "in tree";
m_qmlEngine->watchHandler()->setCurrentItem(iname);
m_objectToSelect = 0;
return true;
} else {
// we may have to fetch it
m_objectToSelect = debugId;
using namespace QmlDebug::Constants;
if (m_engineClient->objectName() == QLatin1String(QDECLARATIVE_ENGINE)) {
// reset current Selection
QString root = m_qmlEngine->watchHandler()->watchItem(QModelIndex())->iname;
m_qmlEngine->watchHandler()->setCurrentItem(root);
} else {
fetchObject(debugId);
}
return false;
}
}
ObjectReference QmlInspectorAgent::objectForId(int objectDebugId) const
{
if (!m_debugIdToIname.contains(objectDebugId))
return ObjectReference(objectDebugId);
int line = -1;
int column = -1;
QString file;
QHashIterator<QPair<QString, int>, QHash<QPair<int, int>, QList<int> > > iter(m_debugIdHash);
while (iter.hasNext()) {
iter.next();
QHashIterator<QPair<int, int>, QList<int> > i(iter.value());
while (i.hasNext()) {
i.next();
if (i.value().contains(objectDebugId)) {
line = i.key().first;
column = i.key().second;
break;
}
}
if (line != -1) {
file = iter.key().first;
break;
}
}
// TODO: Set correct parentId
return ObjectReference(objectDebugId, WatchItem::InvalidId,
FileReference(QUrl::fromLocalFile(file), line, column));
}
void QmlInspectorAgent::addObjectWatch(int objectDebugId)
{
qCDebug(qmlInspectorLog) << __FUNCTION__ << '(' << objectDebugId << ')';
if (objectDebugId == WatchItem::InvalidId)
return;
if (!isConnected() || !boolSetting(ShowQmlObjectTree))
return;
// already set
if (m_objectWatches.contains(objectDebugId))
return;
// is flooding the debugging output log!
// log(LogSend, QString::fromLatin1("WATCH_PROPERTY %1").arg(objectDebugId));
if (m_engineClient->addWatch(objectDebugId))
m_objectWatches.append(objectDebugId);
}
QString QmlInspectorAgent::displayName(int objectDebugId) const
{
if (!isConnected() || !boolSetting(ShowQmlObjectTree))
return QString();
if (m_debugIdToIname.contains(objectDebugId)) {
const WatchItem *item = m_qmlEngine->watchHandler()->findItem(
m_debugIdToIname.value(objectDebugId));
QTC_ASSERT(item, return QString());
return item->name;
}
return QString();
}
void QmlInspectorAgent::updateState()
{
if (m_engineClient
&& (m_engineClient->state() == QmlDebugClient::Enabled)
&& boolSetting(ShowQmlObjectTree)) {
reloadEngines();
} else {
clearObjectTree();
}
}
void QmlInspectorAgent::onResult(quint32 queryId, const QVariant &value,
const QByteArray &type)
{
qCDebug(qmlInspectorLog) << __FUNCTION__ << "() ...";
if (type == "FETCH_OBJECT_R") {
log(LogReceive, QString("FETCH_OBJECT_R %1").arg(
qvariant_cast<ObjectReference>(value).idString()));
} else if (type == "SET_BINDING_R"
|| type == "RESET_BINDING_R"
|| type == "SET_METHOD_BODY_R") {
// FIXME: This is not supported anymore.
QString msg = QLatin1String(type) + tr("Success:");
msg += QLatin1Char(' ');
msg += value.toBool() ? QLatin1Char('1') : QLatin1Char('0');
// if (!value.toBool())
// emit automaticUpdateFailed();
log(LogReceive, msg);
} else {
log(LogReceive, QLatin1String(type));
}
if (m_objectTreeQueryIds.contains(queryId)) {
m_objectTreeQueryIds.removeOne(queryId);
if (value.type() == QVariant::List) {
const QVariantList objList = value.toList();
for (const QVariant &var : objList) {
// TODO: check which among the list is the actual
// object that needs to be selected.
verifyAndInsertObjectInTree(qvariant_cast<ObjectReference>(var));
}
} else {
verifyAndInsertObjectInTree(qvariant_cast<ObjectReference>(value));
}
} else if (queryId == m_engineQueryId) {
m_engineQueryId = 0;
QList<EngineReference> engines = qvariant_cast<QList<EngineReference> >(value);
QTC_ASSERT(engines.count(), return);
// only care about first engine atm
m_engine = engines.at(0);
queryEngineContext();
} else if (queryId == m_rootContextQueryId) {
m_rootContextQueryId = 0;
clearObjectTree();
updateObjectTree(qvariant_cast<ContextReference>(value));
} else {
m_qmlEngine->expressionEvaluated(queryId, value);
}
qCDebug(qmlInspectorLog) << __FUNCTION__ << "done";
}
void QmlInspectorAgent::newObject(int engineId, int /*objectId*/, int /*parentId*/)
{
qCDebug(qmlInspectorLog) << __FUNCTION__ << "()";
log(LogReceive, QLatin1String("OBJECT_CREATED"));
if (m_engine.debugId() != engineId)
return;
// TODO: FIX THIS for qt 5.x (Needs update in the qt side)
m_delayQueryTimer.start();
}
static void sortChildrenIfNecessary(WatchItem *propertiesWatch)
{
if (boolSetting(SortStructMembers)) {
propertiesWatch->sortChildren([](const WatchItem *item1, const WatchItem *item2) {
return item1->name < item2->name;
});
}
}
static bool insertChildren(WatchItem *parent, const QVariant &value)
{
switch (value.type()) {
case QVariant::Map: {
const QVariantMap map = value.toMap();
for (auto it = map.begin(), end = map.end(); it != end; ++it) {
auto child = new WatchItem;
child->name = it.key();
child->value = it.value().toString();
child->type = QLatin1String(it.value().typeName());
child->valueEditable = false;
child->wantsChildren = insertChildren(child, it.value());
parent->appendChild(child);
}
sortChildrenIfNecessary(parent);
return true;
}
case QVariant::List: {
const QVariantList list = value.toList();
for (int i = 0, end = list.size(); i != end; ++i) {
auto child = new WatchItem;
const QVariant &value = list.at(i);
child->arrayIndex = i;
child->value = value.toString();
child->type = QLatin1String(value.typeName());
child->valueEditable = false;
child->wantsChildren = insertChildren(child, value);
parent->appendChild(child);
}
return true;
}
default:
return false;
}
}
void QmlInspectorAgent::onValueChanged(int debugId, const QByteArray &propertyName,
const QVariant &value)
{
const QString iname = m_debugIdToIname.value(debugId) +
".[properties]." + QString::fromLatin1(propertyName);
WatchHandler *watchHandler = m_qmlEngine->watchHandler();
qCDebug(qmlInspectorLog)
<< __FUNCTION__ << '(' << debugId << ')' << iname
<< value.toString();
if (WatchItem *item = watchHandler->findItem(iname)) {
item->value = value.toString();
item->removeChildren();
item->wantsChildren = insertChildren(item, value);
item->update();
}
}
void QmlInspectorAgent::reloadEngines()
{
qCDebug(qmlInspectorLog) << __FUNCTION__ << "()";
if (!isConnected())
return;
log(LogSend, "LIST_ENGINES");
m_engineQueryId = m_engineClient->queryAvailableEngines();
}
void QmlInspectorAgent::queryEngineContext()
{
qCDebug(qmlInspectorLog) << __FUNCTION__;
if (!isConnected() || !boolSetting(ShowQmlObjectTree))
return;
log(LogSend, QLatin1String("LIST_OBJECTS"));
m_rootContextQueryId
= m_engineClient->queryRootContexts(m_engine);
}
void QmlInspectorAgent::fetchObject(int debugId)
{
qCDebug(qmlInspectorLog) << __FUNCTION__ << '(' << debugId << ')';
if (!isConnected() || !boolSetting(ShowQmlObjectTree))
return;
log(LogSend, QLatin1String("FETCH_OBJECT ") + QString::number(debugId));
quint32 queryId = m_engineClient->queryObject(debugId);
qCDebug(qmlInspectorLog) << __FUNCTION__ << '(' << debugId << ')'
<< " - query id" << queryId;
m_objectTreeQueryIds << queryId;
}
void QmlInspectorAgent::updateObjectTree(const ContextReference &context)
{
qCDebug(qmlInspectorLog) << __FUNCTION__ << '(' << context << ')';
if (!isConnected() || !boolSetting(ShowQmlObjectTree))
return;
foreach (const ObjectReference & obj, context.objects())
verifyAndInsertObjectInTree(obj);
foreach (const ContextReference &child, context.contexts())
updateObjectTree(child);
}
void QmlInspectorAgent::verifyAndInsertObjectInTree(const ObjectReference &object)
{
qCDebug(qmlInspectorLog) << __FUNCTION__ << '(' << object << ')';
if (!object.isValid())
return;
// Find out the correct position in the tree
// Objects are inserted to the tree if they satisfy one of the two conditions.
// Condition 1: Object is a root object i.e. parentId == WatchItem::InvalidId.
// Condition 2: Object has an expanded parent i.e. siblings are known.
// If the two conditions are not met then we push the object to a stack and recursively
// fetch parents till we find a previously expanded parent.
WatchHandler *handler = m_qmlEngine->watchHandler();
const int parentId = object.parentId();
const int objectDebugId = object.debugId();
if (m_debugIdToIname.contains(parentId)) {
QString parentIname = m_debugIdToIname.value(parentId);
if (parentId != WatchItem::InvalidId && !handler->isExpandedIName(parentIname)) {
m_objectStack.push(object);
handler->fetchMore(parentIname);
return; // recursive
}
insertObjectInTree(object);
} else {
m_objectStack.push(object);
fetchObject(parentId);
return; // recursive
}
if (!m_objectStack.isEmpty()) {
const ObjectReference &top = m_objectStack.top();
// We want to expand only a particular branch and not the whole tree. Hence, we do not
// expand siblings.
if (object.children().contains(top)) {
QString objectIname = m_debugIdToIname.value(objectDebugId);
if (!handler->isExpandedIName(objectIname)) {
handler->fetchMore(objectIname);
} else {
verifyAndInsertObjectInTree(m_objectStack.pop());
return; // recursive
}
}
}
}
void QmlInspectorAgent::insertObjectInTree(const ObjectReference &object)
{
qCDebug(qmlInspectorLog) << __FUNCTION__ << '(' << object << ')';
const int objectDebugId = object.debugId();
const int parentId = parentIdForIname(m_debugIdToIname.value(objectDebugId));
QElapsedTimer timeElapsed;
bool printTime = qmlInspectorLog().isDebugEnabled();
if (printTime)
timeElapsed.start();
addWatchData(object, m_debugIdToIname.value(parentId), true);
qCDebug(qmlInspectorLog) << __FUNCTION__ << "Time: Build Watch Data took "
<< timeElapsed.elapsed() << " ms";
if (printTime)
timeElapsed.start();
buildDebugIdHashRecursive(object);
qCDebug(qmlInspectorLog) << __FUNCTION__ << "Time: Build Debug Id Hash took "
<< timeElapsed.elapsed() << " ms";
if (printTime)
timeElapsed.start();
qCDebug(qmlInspectorLog) << __FUNCTION__ << "Time: Insertion took "
<< timeElapsed.elapsed() << " ms";
if (object.debugId() == m_debugIdToSelect) {
m_debugIdToSelect = WatchItem::InvalidId;
selectObject(object, m_targetToSync);
}
if (m_debugIdToIname.contains(m_objectToSelect)) {
// select item in view
QString iname = m_debugIdToIname.value(m_objectToSelect);
qCDebug(qmlInspectorLog) << " selecting" << iname << "in tree";
m_qmlEngine->watchHandler()->setCurrentItem(iname);
m_objectToSelect = WatchItem::InvalidId;
}
m_qmlEngine->watchHandler()->updateLocalsWindow();
m_qmlEngine->watchHandler()->reexpandItems();
}
void QmlInspectorAgent::buildDebugIdHashRecursive(const ObjectReference &ref)
{
qCDebug(qmlInspectorLog) << __FUNCTION__ << '(' << ref << ')';
QUrl fileUrl = ref.source().url();
int lineNum = ref.source().lineNumber();
int colNum = ref.source().columnNumber();
int rev = 0;
// handle the case where the url contains the revision number encoded.
//(for object created by the debugger)
static QRegExp rx(QLatin1String("(.*)_(\\d+):(\\d+)$"));
if (rx.exactMatch(fileUrl.path())) {
fileUrl.setPath(rx.cap(1));
rev = rx.cap(2).toInt();
lineNum += rx.cap(3).toInt() - 1;
}
const QString filePath
= m_qmlEngine->toFileInProject(fileUrl);
// append the debug ids in the hash
QPair<QString, int> file = qMakePair<QString, int>(filePath, rev);
QPair<int, int> location = qMakePair<int, int>(lineNum, colNum);
if (!m_debugIdHash[file][location].contains(ref.debugId()))
m_debugIdHash[file][location].append(ref.debugId());
m_debugIdLocations.insert(ref.debugId(), FileReference(filePath, lineNum, colNum));
foreach (const ObjectReference &it, ref.children())
buildDebugIdHashRecursive(it);
}
static QString buildIName(const QString &parentIname, int debugId)
{
if (parentIname.isEmpty())
return "inspect." + QString::number(debugId);
return parentIname + "." + QString::number(debugId);
}
static QString buildIName(const QString &parentIname, const QString &name)
{
return parentIname + "." + name;
}
void QmlInspectorAgent::addWatchData(const ObjectReference &obj,
const QString &parentIname,
bool append)
{
qCDebug(qmlInspectorLog) << '(' << obj << parentIname << ')';
QTC_ASSERT(m_qmlEngine, return);
int objDebugId = obj.debugId();
QString objIname = buildIName(parentIname, objDebugId);
if (append) {
QString name = obj.idString();
if (name.isEmpty())
name = obj.className();
if (name.isEmpty())
name = obj.name();
if (name.isEmpty()) {
FileReference file = obj.source();
name = file.url().fileName() + ':' + QString::number(file.lineNumber());
}
if (name.isEmpty())
name = tr("<anonymous>");
// object
auto objWatch = new WatchItem;
objWatch->iname = objIname;
objWatch->name = name;
objWatch->id = objDebugId;
objWatch->exp = name;
objWatch->type = obj.className();
objWatch->value = "object";
objWatch->wantsChildren = true;
m_qmlEngine->watchHandler()->insertItem(objWatch);
addObjectWatch(objWatch->id);
if (m_debugIdToIname.contains(objDebugId)) {
// The data needs to be removed since we now know the parent and
// hence we can insert the data in the correct position
const QString oldIname = m_debugIdToIname.value(objDebugId);
if (oldIname != objIname)
m_qmlEngine->watchHandler()->removeItemByIName(oldIname);
}
m_debugIdToIname.insert(objDebugId, objIname);
}
if (!m_qmlEngine->watchHandler()->isExpandedIName(objIname)) {
// we don't know the children yet. Not adding the 'properties'
// element makes sure we're queried on expansion.
if (obj.needsMoreData())
return;
}
// properties
if (append && obj.properties().count()) {
QString iname = objIname + ".[properties]";
auto propertiesWatch = new WatchItem;
propertiesWatch->iname = iname;
propertiesWatch->name = tr("Properties");
propertiesWatch->id = objDebugId;
propertiesWatch->value = "list";
propertiesWatch->wantsChildren = true;
foreach (const PropertyReference &property, obj.properties()) {
const QString propertyName = property.name();
if (propertyName.isEmpty())
continue;
auto propertyWatch = new WatchItem;
propertyWatch->iname = buildIName(iname, propertyName);
propertyWatch->name = propertyName;
propertyWatch->id = objDebugId;
propertyWatch->exp = propertyName;
propertyWatch->type = property.valueTypeName();
propertyWatch->value = property.value().toString();
propertyWatch->wantsChildren = insertChildren(propertyWatch, property.value());
propertiesWatch->appendChild(propertyWatch);
}
sortChildrenIfNecessary(propertiesWatch);
m_qmlEngine->watchHandler()->insertItem(propertiesWatch);
}
// recurse
foreach (const ObjectReference &child, obj.children())
addWatchData(child, objIname, append);
}
void QmlInspectorAgent::log(QmlInspectorAgent::LogDirection direction,
const QString &message)
{
QString msg = "Inspector";
if (direction == LogSend)
msg += " sending ";
else
msg += " receiving ";
msg += message;
if (m_qmlEngine)
m_qmlEngine->showMessage(msg, LogDebug);
}
bool QmlInspectorAgent::isConnected() const
{
return m_engineClient && m_engineClient->state() == QmlDebugClient::Enabled;
}
void QmlInspectorAgent::clearObjectTree()
{
if (m_qmlEngine)
m_qmlEngine->watchHandler()->removeAllData(true);
m_objectTreeQueryIds.clear();
m_fetchDataIds.clear();
int old_count = m_debugIdHash.count();
m_debugIdHash.clear();
m_debugIdHash.reserve(old_count + 1);
m_debugIdToIname.clear();
m_debugIdToIname.insert(WatchItem::InvalidId, "inspect");
m_objectStack.clear();
m_objectWatches.clear();
}
void QmlInspectorAgent::clientStateChanged(QmlDebugClient::State state)
{
QString serviceName;
float version = 0;
if (auto client = qobject_cast<QmlDebugClient*>(sender())) {
serviceName = client->name();
version = client->serviceVersion();
}
m_qmlEngine->logServiceStateChange(serviceName, version, state);
}
void QmlInspectorAgent::toolsClientStateChanged(QmlDebugClient::State state)
{
auto client = qobject_cast<BaseToolsClient*>(sender());
QTC_ASSERT(client, return);
if (state == QmlDebugClient::Enabled) {
m_toolsClient = client;
connect(client, &BaseToolsClient::currentObjectsChanged,
this, &QmlInspectorAgent::selectObjectsFromToolsClient);
connect(client, &BaseToolsClient::logActivity,
m_qmlEngine.data(), &QmlEngine::logServiceActivity);
connect(client, &BaseToolsClient::reloaded,
this, &QmlInspectorAgent::onReloaded);
// register actions here
// because there can be multiple QmlEngines
// at the same time (but hopefully one one is connected)
Core::ActionManager::registerAction(m_selectAction,
Core::Id(Constants::QML_SELECTTOOL),
m_inspectorToolsContext);
Core::ActionManager::registerAction(m_zoomAction, Core::Id(Constants::QML_ZOOMTOOL),
m_inspectorToolsContext);
Core::ActionManager::registerAction(m_showAppOnTopAction,
Core::Id(Constants::QML_SHOW_APP_ON_TOP),
m_inspectorToolsContext);
Core::ICore::addAdditionalContext(m_inspectorToolsContext);
m_toolsClientConnected = true;
Debugger: Make most views per-engine instead of singletons This is a step towards properly supporting multiple debugger sessions side-by-side. The combined C++-and-QML engine has been removed, instead a combined setup creates now two individual engines, under a single DebuggerRunTool but mostly independent with no combined state machine. This requires a few more clicks in some cases, but makes it easier to direct e.g. interrupt requests to the interesting engine. Care has been taken to not change the UX of the single debugger session use case if possible. The fat debug button operates as-before in that case, i.e. switches to Interrupt if the single active runconfiguration runs in the debugger etc. Most views are made per-engine, running an engine creates a new Perspective, which is destroyed when the run control dies. The snapshot view remains global and becomes primary source of information on a "current engine" that receives all menu and otherwise global input. There is a new global "Breakpoint Preset" view containing all "static" breakpoint data. When an engine starts up it "claims" breakpoint it believes it can handle, but operates on a copy of the static data. The markers of the static version are suppressed as long as an engine controls a breakpoint (that inclusive all resolved locations), but are re-instatet once the engine quits. The old Breakpoint class that already contained this split per-instance was split into a new Breakpoint and a GlobalBreakpoint class, with a per-engine model for Breakpoints, and a singleton model containing GlobalBreakpoints. There is a new CppDebuggerEngine intermediate level serving as base for C++ (or, rather, "compiled") binary debugging, i.e. {Gdb,Lldb,Cdb}Engine, taking over bits of the current DebuggerEngine base that are not applicable to non-binary debuggers. Change-Id: I9994f4c188379b4aee0c4f379edd4759fbb0bd43 Reviewed-by: Christian Stenger <christian.stenger@qt.io> Reviewed-by: hjk <hjk@qt.io>
2018-07-31 12:30:48 +02:00
enableTools(m_qmlEngine->state() == InferiorRunOk);
if (m_showAppOnTopAction->isChecked())
m_toolsClient->showAppOnTop(true);
} else if (m_toolsClientConnected && client == m_toolsClient) {
disconnect(client, &BaseToolsClient::currentObjectsChanged,
this, &QmlInspectorAgent::selectObjectsFromToolsClient);
disconnect(client, &BaseToolsClient::logActivity,
m_qmlEngine.data(), &QmlEngine::logServiceActivity);
Core::ActionManager::unregisterAction(m_selectAction, Core::Id(Constants::QML_SELECTTOOL));
Core::ActionManager::unregisterAction(m_zoomAction, Core::Id(Constants::QML_ZOOMTOOL));
Core::ActionManager::unregisterAction(m_showAppOnTopAction,
Core::Id(Constants::QML_SHOW_APP_ON_TOP));
Core::ICore::removeAdditionalContext(m_inspectorToolsContext);
enableTools(false);
m_toolsClientConnected = false;
m_selectAction->setCheckable(false);
m_zoomAction->setCheckable(false);
m_showAppOnTopAction->setCheckable(false);
}
}
void QmlInspectorAgent::engineClientStateChanged(QmlDebugClient::State state)
{
auto client = qobject_cast<BaseEngineDebugClient*>(sender());
if (state == QmlDebugClient::Enabled && !m_engineClientConnected) {
// We accept the first client that is enabled and reject the others.
QTC_ASSERT(client, return);
setActiveEngineClient(client);
} else if (m_engineClientConnected && client == m_engineClient) {
m_engineClientConnected = false;
}
}
void QmlInspectorAgent::selectObjectsFromToolsClient(const QList<int> &debugIds)
{
if (debugIds.isEmpty())
return;
m_targetToSync = EditorTarget;
m_debugIdToSelect = debugIds.first();
selectObject(objectForId(m_debugIdToSelect), EditorTarget);
}
void QmlInspectorAgent::onSelectActionTriggered(bool checked)
{
QTC_ASSERT(m_toolsClient, return);
if (checked) {
m_toolsClient->setDesignModeBehavior(true);
m_toolsClient->changeToSelectTool();
m_zoomAction->setChecked(false);
} else {
m_toolsClient->setDesignModeBehavior(false);
}
}
void QmlInspectorAgent::onZoomActionTriggered(bool checked)
{
QTC_ASSERT(m_toolsClient, return);
if (checked) {
m_toolsClient->setDesignModeBehavior(true);
m_toolsClient->changeToZoomTool();
m_selectAction->setChecked(false);
} else {
m_toolsClient->setDesignModeBehavior(false);
}
}
void QmlInspectorAgent::onShowAppOnTopChanged(bool checked)
{
QTC_ASSERT(m_toolsClient, return);
m_toolsClient->showAppOnTop(checked);
}
void QmlInspectorAgent::setActiveEngineClient(BaseEngineDebugClient *client)
{
if (m_engineClient == client)
return;
if (m_engineClient) {
disconnect(m_engineClient, &BaseEngineDebugClient::newState,
this, &QmlInspectorAgent::updateState);
disconnect(m_engineClient, &BaseEngineDebugClient::result,
this, &QmlInspectorAgent::onResult);
disconnect(m_engineClient, &BaseEngineDebugClient::newObject,
this, &QmlInspectorAgent::newObject);
disconnect(m_engineClient, &BaseEngineDebugClient::valueChanged,
this, &QmlInspectorAgent::onValueChanged);
}
m_engineClient = client;
if (m_engineClient) {
connect(m_engineClient, &BaseEngineDebugClient::newState,
this, &QmlInspectorAgent::updateState);
connect(m_engineClient, &BaseEngineDebugClient::result,
this, &QmlInspectorAgent::onResult);
connect(m_engineClient, &BaseEngineDebugClient::newObject,
this, &QmlInspectorAgent::newObject);
connect(m_engineClient, &BaseEngineDebugClient::valueChanged,
this, &QmlInspectorAgent::onValueChanged);
}
updateState();
m_engineClientConnected = true;
}
void QmlInspectorAgent::jumpToObjectDefinitionInEditor(
const FileReference &objSource, int debugId)
{
Debugger: Make most views per-engine instead of singletons This is a step towards properly supporting multiple debugger sessions side-by-side. The combined C++-and-QML engine has been removed, instead a combined setup creates now two individual engines, under a single DebuggerRunTool but mostly independent with no combined state machine. This requires a few more clicks in some cases, but makes it easier to direct e.g. interrupt requests to the interesting engine. Care has been taken to not change the UX of the single debugger session use case if possible. The fat debug button operates as-before in that case, i.e. switches to Interrupt if the single active runconfiguration runs in the debugger etc. Most views are made per-engine, running an engine creates a new Perspective, which is destroyed when the run control dies. The snapshot view remains global and becomes primary source of information on a "current engine" that receives all menu and otherwise global input. There is a new global "Breakpoint Preset" view containing all "static" breakpoint data. When an engine starts up it "claims" breakpoint it believes it can handle, but operates on a copy of the static data. The markers of the static version are suppressed as long as an engine controls a breakpoint (that inclusive all resolved locations), but are re-instatet once the engine quits. The old Breakpoint class that already contained this split per-instance was split into a new Breakpoint and a GlobalBreakpoint class, with a per-engine model for Breakpoints, and a singleton model containing GlobalBreakpoints. There is a new CppDebuggerEngine intermediate level serving as base for C++ (or, rather, "compiled") binary debugging, i.e. {Gdb,Lldb,Cdb}Engine, taking over bits of the current DebuggerEngine base that are not applicable to non-binary debuggers. Change-Id: I9994f4c188379b4aee0c4f379edd4759fbb0bd43 Reviewed-by: Christian Stenger <christian.stenger@qt.io> Reviewed-by: hjk <hjk@qt.io>
2018-07-31 12:30:48 +02:00
const QString fileName = m_qmlEngine->toFileInProject(objSource.url());
Core::EditorManager::openEditorAt(fileName, objSource.lineNumber());
if (debugId != WatchItem::InvalidId && debugId != m_currentSelectedDebugId) {
m_currentSelectedDebugId = debugId;
m_currentSelectedDebugName = displayName(debugId);
}
}
void QmlInspectorAgent::selectObject(const ObjectReference &obj, SelectionTarget target)
{
if (m_toolsClient && target == ToolTarget)
m_toolsClient->setObjectIdList(QList<ObjectReference>() << obj);
if (target == EditorTarget)
jumpToObjectDefinitionInEditor(obj.source());
selectObjectInTree(obj.debugId());
}
void QmlInspectorAgent::enableTools(const bool enable)
{
if (!m_toolsClientConnected)
return;
m_selectAction->setEnabled(enable);
m_showAppOnTopAction->setEnabled(enable);
// only enable zoom action for Qt 4.x/old client
// (zooming is integrated into selection tool in Qt 5).
if (!qobject_cast<QmlToolsClient*>(m_toolsClient))
m_zoomAction->setEnabled(enable);
}
void QmlInspectorAgent::onReloaded()
{
reloadEngines();
}
} // namespace Internal
} // namespace Debugger