qmlinspectoragent.cpp 32.1 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
2
**
3 4
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
7
**
hjk's avatar
hjk committed
8 9 10 11
** 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
12 13 14
** 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.
15
**
16 17 18 19 20 21 22
** 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.
23
**
hjk's avatar
hjk committed
24
****************************************************************************/
25 26

#include "qmlinspectoragent.h"
27
#include "qmlengine.h"
28

29 30 31
#include <debugger/debuggeractions.h>
#include <debugger/debuggercore.h>
#include <debugger/debuggerengine.h>
32
#include <debugger/debuggerruncontrol.h>
33
#include <debugger/watchhandler.h>
34

35 36 37 38 39 40 41 42 43
#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>
44
#include <qmldebug/qmldebugconstants.h>
45 46 47
#include <qmldebug/qmlenginedebugclient.h>
#include <qmldebug/qmltoolsclient.h>

48
#include <utils/fileutils.h>
49 50
#include <utils/qtcassert.h>
#include <utils/savedaction.h>
51

52
#include <QElapsedTimer>
53
#include <QFileInfo>
54
#include <QLoggingCategory>
55

Kai Koehne's avatar
Kai Koehne committed
56
using namespace QmlDebug;
57
using namespace QmlDebug::Constants;
Kai Koehne's avatar
Kai Koehne committed
58

59 60 61
namespace Debugger {
namespace Internal {

62
Q_LOGGING_CATEGORY(qmlInspectorLog, "qtc.dbg.qmlinspector")
63 64 65 66

/*!
 * DebuggerAgent updates the watchhandler with the object tree data.
 */
67 68
QmlInspectorAgent::QmlInspectorAgent(QmlEngine *engine, QmlDebugConnection *connection)
    : m_qmlEngine(engine)
69 70 71
    , m_engineClient(0)
    , m_engineQueryId(0)
    , m_rootContextQueryId(0)
72
    , m_objectToSelect(WatchItem::InvalidId)
73
    , m_masterEngine(engine->masterEngine())
74 75
    , m_toolsClient(0)
    , m_targetToSync(NoTarget)
76 77
    , m_debugIdToSelect(WatchItem::InvalidId)
    , m_currentSelectedDebugId(WatchItem::InvalidId)
78 79 80 81 82 83
    , m_toolsClientConnected(false)
    , m_inspectorToolsContext("Debugger.QmlInspector")
    , m_selectAction(new QAction(this))
    , m_zoomAction(new QAction(this))
    , m_showAppOnTopAction(action(ShowAppOnTop))
    , m_engineClientConnected(false)
84
{
85
    m_debugIdToIname.insert(WatchItem::InvalidId, "inspect");
hjk's avatar
hjk committed
86
    connect(action(ShowQmlObjectTree),
87
            &Utils::SavedAction::valueChanged, this, &QmlInspectorAgent::updateState);
88 89
    connect(action(SortStructMembers), &Utils::SavedAction::valueChanged,
            this, &QmlInspectorAgent::updateState);
90
    m_delayQueryTimer.setSingleShot(true);
91
    m_delayQueryTimer.setInterval(100);
Montel Laurent's avatar
Montel Laurent committed
92 93
    connect(&m_delayQueryTimer, &QTimer::timeout,
            this, &QmlInspectorAgent::queryEngineContext);
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151

    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);
152 153 154 155 156
}

quint32 QmlInspectorAgent::queryExpressionResult(int debugId,
                                                 const QString &expression)
{
157 158 159
    if (!m_engineClient)
        return 0;

160 161 162
    qCDebug(qmlInspectorLog)
            << __FUNCTION__ << '(' << debugId << expression
            << m_engine.debugId() << ')';
163 164

    return m_engineClient->queryExpressionResult(debugId, expression,
165
                                                 m_engine.debugId());
166 167
}

168
void QmlInspectorAgent::assignValue(const WatchItem *data,
169 170
                                    const QString &expr, const QVariant &valueV)
{
171 172
    qCDebug(qmlInspectorLog)
            << __FUNCTION__ << '(' << data->id << ')' << data->iname;
173

174
    if (data->id != WatchItem::InvalidId) {
175
        QString val(valueV.toString());
176
        QString expression = QString("%1 = %2;").arg(expr).arg(val);
177 178 179 180
        queryExpressionResult(data->id, expression);
    }
}

181
static int parentIdForIname(const QString &iname)
182 183 184 185
{
    // Extract the parent id
    int lastIndex = iname.lastIndexOf('.');
    int secondLastIndex = iname.lastIndexOf('.', lastIndex - 1);
186 187
    int parentId = WatchItem::InvalidId;
    if (secondLastIndex != WatchItem::InvalidId)
188 189 190 191
        parentId = iname.mid(secondLastIndex + 1, lastIndex - secondLastIndex - 1).toInt();
    return parentId;
}

192
void QmlInspectorAgent::updateWatchData(const WatchItem &data)
193
{
194
    qCDebug(qmlInspectorLog) << __FUNCTION__ << '(' << data.id << ')';
195

196
    if (data.id != WatchItem::InvalidId && !m_fetchDataIds.contains(data.id)) {
197
        // objects
hjk's avatar
hjk committed
198
        m_fetchDataIds << data.id;
199
        fetchObject(data.id);
200 201 202
    }
}

203
void QmlInspectorAgent::watchDataSelected(qint64 id)
204
{
205
    qCDebug(qmlInspectorLog) << __FUNCTION__ << '(' << id << ')';
206

207
    if (id != WatchItem::InvalidId) {
208
        QTC_ASSERT(m_debugIdLocations.keys().contains(id), return);
209
        jumpToObjectDefinitionInEditor(m_debugIdLocations.value(id), id);
210 211
        if (m_toolsClient)
            m_toolsClient->setObjectIdList({ObjectReference(static_cast<int>(id))});
212 213 214
    }
}

215
bool QmlInspectorAgent::selectObjectInTree(int debugId)
216
{
217 218 219
    qCDebug(qmlInspectorLog) << __FUNCTION__ << '(' << debugId << ')' << endl
                             << "  " << debugId << "already fetched? "
                             << m_debugIdToIname.contains(debugId);
220 221

    if (m_debugIdToIname.contains(debugId)) {
222
        QString iname = m_debugIdToIname.value(debugId);
223
        QTC_ASSERT(iname.startsWith("inspect."), qDebug() << iname);
224
        qCDebug(qmlInspectorLog) << "  selecting" << iname << "in tree";
225
        m_qmlEngine->watchHandler()->setCurrentItem(iname);
226
        m_objectToSelect = 0;
227
        return true;
228
    } else {
229
        // we may have to fetch it
230
        m_objectToSelect = debugId;
231 232 233
        using namespace QmlDebug::Constants;
        if (m_engineClient->objectName() == QLatin1String(QDECLARATIVE_ENGINE)) {
            // reset current Selection
234
            QString root = m_qmlEngine->watchHandler()->watchItem(QModelIndex())->iname;
235
            m_qmlEngine->watchHandler()->setCurrentItem(root);
236 237 238
        } else {
            fetchObject(debugId);
        }
239
        return false;
240 241 242
    }
}

243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268
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
269
    return ObjectReference(objectDebugId, WatchItem::InvalidId,
270 271 272
                           FileReference(QUrl::fromLocalFile(file), line, column));
}

273
void QmlInspectorAgent::addObjectWatch(int objectDebugId)
274
{
275
    qCDebug(qmlInspectorLog) << __FUNCTION__ << '(' << objectDebugId << ')';
276

277
    if (objectDebugId == WatchItem::InvalidId)
278
        return;
279

hjk's avatar
hjk committed
280
    if (!isConnected() || !boolSetting(ShowQmlObjectTree))
281
        return;
282 283 284

    // already set
    if (m_objectWatches.contains(objectDebugId))
285
        return;
286 287

    // is flooding the debugging output log!
288
    // log(LogSend, QString::fromLatin1("WATCH_PROPERTY %1").arg(objectDebugId));
289

290
    if (m_engineClient->addWatch(objectDebugId))
291 292 293
        m_objectWatches.append(objectDebugId);
}

294 295
QString QmlInspectorAgent::displayName(int objectDebugId) const
{
hjk's avatar
hjk committed
296
    if (!isConnected() || !boolSetting(ShowQmlObjectTree))
297 298 299
        return QString();

    if (m_debugIdToIname.contains(objectDebugId)) {
300
        const WatchItem *item = m_qmlEngine->watchHandler()->findItem(
301
                    m_debugIdToIname.value(objectDebugId));
302 303
        QTC_ASSERT(item, return QString());
        return item->name;
304 305 306 307
    }
    return QString();
}

308
void QmlInspectorAgent::updateState()
309 310
{
    if (m_engineClient
311
            && (m_engineClient->state() == QmlDebugClient::Enabled)
hjk's avatar
hjk committed
312
            && boolSetting(ShowQmlObjectTree)) {
313 314
        reloadEngines();
    } else {
315
        clearObjectTree();
316 317 318 319 320 321
    }
}

void QmlInspectorAgent::onResult(quint32 queryId, const QVariant &value,
                                 const QByteArray &type)
{
322
    qCDebug(qmlInspectorLog) << __FUNCTION__ << "() ...";
323

324
    if (type == "FETCH_OBJECT_R") {
325
        log(LogReceive, QString("FETCH_OBJECT_R %1").arg(
Kai Koehne's avatar
Kai Koehne committed
326
                qvariant_cast<ObjectReference>(value).idString()));
327 328 329
    } else if (type == "SET_BINDING_R"
               || type == "RESET_BINDING_R"
               || type == "SET_METHOD_BODY_R") {
330
        // FIXME: This is not supported anymore.
331 332
        QString msg = QLatin1String(type) + tr("Success:");
        msg += QLatin1Char(' ');
333
        msg += value.toBool() ? QLatin1Char('1') : QLatin1Char('0');
334 335
        // if (!value.toBool())
        //     emit automaticUpdateFailed();
336
        log(LogReceive, msg);
337 338 339 340 341
    } else {
        log(LogReceive, QLatin1String(type));
    }

    if (m_objectTreeQueryIds.contains(queryId)) {
342
        m_objectTreeQueryIds.removeOne(queryId);
343 344
        if (value.type() == QVariant::List) {
            QVariantList objList = value.toList();
345
            foreach (const QVariant &var, objList) {
346 347
                // TODO: check which among the list is the actual
                // object that needs to be selected.
348
                verifyAndInsertObjectInTree(qvariant_cast<ObjectReference>(var));
349 350
            }
        } else {
351
            verifyAndInsertObjectInTree(qvariant_cast<ObjectReference>(value));
352
        }
353 354 355 356 357 358 359 360 361 362
    } 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();
363
        updateObjectTree(qvariant_cast<ContextReference>(value));
364
    } else {
365
        m_qmlEngine->expressionEvaluated(queryId, value);
366 367
    }

368
    qCDebug(qmlInspectorLog) << __FUNCTION__ << "done";
369 370
}

371
void QmlInspectorAgent::newObject(int engineId, int /*objectId*/, int /*parentId*/)
372
{
373
    qCDebug(qmlInspectorLog) << __FUNCTION__ << "()";
374

375
    log(LogReceive, QLatin1String("OBJECT_CREATED"));
376 377 378

    if (m_engine.debugId() != engineId)
        return;
379

380 381
    // TODO: FIX THIS for qt 5.x (Needs update in the qt side)
    m_delayQueryTimer.start();
382 383
}

384 385 386
void QmlInspectorAgent::onValueChanged(int debugId, const QByteArray &propertyName,
                                       const QVariant &value)
{
387
    const QString iname = m_debugIdToIname.value(debugId) +
Erik Verbruggen's avatar
Erik Verbruggen committed
388
            ".[properties]." + QString::fromLatin1(propertyName);
389
    WatchHandler *watchHandler = m_qmlEngine->watchHandler();
390 391 392
    qCDebug(qmlInspectorLog)
            << __FUNCTION__ << '(' << debugId << ')' << iname
            << value.toString();
393 394 395
    if (WatchItem *item = watchHandler->findItem(iname)) {
        item->value = value.toString();
        item->update();
396 397 398
    }
}

399 400
void QmlInspectorAgent::reloadEngines()
{
401
    qCDebug(qmlInspectorLog) << __FUNCTION__ << "()";
402 403 404 405

    if (!isConnected())
        return;

406
    log(LogSend, "LIST_ENGINES");
407 408 409 410

    m_engineQueryId = m_engineClient->queryAvailableEngines();
}

411
void QmlInspectorAgent::queryEngineContext()
412
{
413
    qCDebug(qmlInspectorLog) << __FUNCTION__;
414

hjk's avatar
hjk committed
415
    if (!isConnected() || !boolSetting(ShowQmlObjectTree))
416 417
        return;

418
    log(LogSend, QLatin1String("LIST_OBJECTS"));
419 420

    m_rootContextQueryId
421
            = m_engineClient->queryRootContexts(m_engine);
422 423
}

424
void QmlInspectorAgent::fetchObject(int debugId)
425
{
426
    qCDebug(qmlInspectorLog) << __FUNCTION__ << '(' << debugId << ')';
427

hjk's avatar
hjk committed
428
    if (!isConnected() || !boolSetting(ShowQmlObjectTree))
429
        return;
430

431
    log(LogSend, QLatin1String("FETCH_OBJECT ") + QString::number(debugId));
432
    quint32 queryId = m_engineClient->queryObject(debugId);
433 434
    qCDebug(qmlInspectorLog) << __FUNCTION__ << '(' << debugId << ')'
                             << " - query id" << queryId;
435
    m_objectTreeQueryIds << queryId;
436 437
}

438
void QmlInspectorAgent::updateObjectTree(const ContextReference &context)
439
{
440
    qCDebug(qmlInspectorLog) << __FUNCTION__ << '(' << context << ')';
441

hjk's avatar
hjk committed
442
    if (!isConnected() || !boolSetting(ShowQmlObjectTree))
443 444
        return;

445
    foreach (const ObjectReference & obj, context.objects())
446
        verifyAndInsertObjectInTree(obj);
447

Kai Koehne's avatar
Kai Koehne committed
448
    foreach (const ContextReference &child, context.contexts())
449
        updateObjectTree(child);
450 451
}

452
void QmlInspectorAgent::verifyAndInsertObjectInTree(const ObjectReference &object)
453
{
454
    qCDebug(qmlInspectorLog) << __FUNCTION__ << '(' << object << ')';
455

456 457
    if (!object.isValid())
        return;
458

459 460
    // Find out the correct position in the tree
    // Objects are inserted to the tree if they satisfy one of the two conditions.
461
    // Condition 1: Object is a root object i.e. parentId == WatchItem::InvalidId.
462 463 464 465
    // 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.

466
    WatchHandler *handler = m_qmlEngine->watchHandler();
467 468 469
    const int parentId = object.parentId();
    const int objectDebugId = object.debugId();
    if (m_debugIdToIname.contains(parentId)) {
470
        QString parentIname = m_debugIdToIname.value(parentId);
471
        if (parentId != WatchItem::InvalidId && !handler->isExpandedIName(parentIname)) {
472
            m_objectStack.push(object);
473
            handler->fetchMore(parentIname);
474 475 476
            return; // recursive
        }
        insertObjectInTree(object);
477

478 479 480 481 482 483 484 485 486 487
    } 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)) {
488
            QString objectIname = m_debugIdToIname.value(objectDebugId);
489
            if (!handler->isExpandedIName(objectIname)) {
490
                handler->fetchMore(objectIname);
491 492 493 494 495 496 497 498 499 500
            } else {
                verifyAndInsertObjectInTree(m_objectStack.pop());
                return; // recursive
            }
        }
    }
}

void QmlInspectorAgent::insertObjectInTree(const ObjectReference &object)
{
501
    qCDebug(qmlInspectorLog) << __FUNCTION__ << '(' << object << ')';
502

503 504
    const int objectDebugId = object.debugId();
    const int parentId = parentIdForIname(m_debugIdToIname.value(objectDebugId));
505

506
    QElapsedTimer timeElapsed;
507 508 509

    bool printTime = qmlInspectorLog().isDebugEnabled();
    if (printTime)
510
        timeElapsed.start();
511
    addWatchData(object, m_debugIdToIname.value(parentId), true);
512 513 514
    qCDebug(qmlInspectorLog) << __FUNCTION__ << "Time: Build Watch Data took "
                             << timeElapsed.elapsed() << " ms";
    if (printTime)
515 516
        timeElapsed.start();
    buildDebugIdHashRecursive(object);
517 518
    qCDebug(qmlInspectorLog) << __FUNCTION__ << "Time: Build Debug Id Hash took "
                             << timeElapsed.elapsed() << " ms";
519

520
    if (printTime)
521
        timeElapsed.start();
522 523
    qCDebug(qmlInspectorLog) << __FUNCTION__ << "Time: Insertion took "
                             << timeElapsed.elapsed() << " ms";
524

525
    if (object.debugId() == m_debugIdToSelect) {
526
        m_debugIdToSelect = WatchItem::InvalidId;
527 528
        selectObject(object, m_targetToSync);
    }
529

530
    if (m_debugIdToIname.contains(m_objectToSelect)) {
531
        // select item in view
532
        QString iname = m_debugIdToIname.value(m_objectToSelect);
533
        qCDebug(qmlInspectorLog) << "  selecting" << iname << "in tree";
534
        m_qmlEngine->watchHandler()->setCurrentItem(iname);
535
        m_objectToSelect = WatchItem::InvalidId;
536
    }
537
    m_qmlEngine->watchHandler()->updateLocalsWindow();
538
    m_qmlEngine->watchHandler()->reexpandItems();
539 540
}

Kai Koehne's avatar
Kai Koehne committed
541
void QmlInspectorAgent::buildDebugIdHashRecursive(const ObjectReference &ref)
542
{
543
    qCDebug(qmlInspectorLog) << __FUNCTION__ << '(' << ref << ')';
544 545 546 547 548 549 550 551

    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)
552
    static QRegExp rx(QLatin1String("(.*)_(\\d+):(\\d+)$"));
553 554 555 556 557 558 559
    if (rx.exactMatch(fileUrl.path())) {
        fileUrl.setPath(rx.cap(1));
        rev = rx.cap(2).toInt();
        lineNum += rx.cap(3).toInt() - 1;
    }

    const QString filePath
560
            = m_qmlEngine->toFileInProject(fileUrl);
561 562 563 564 565 566

    // 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());
567
    m_debugIdLocations.insert(ref.debugId(), FileReference(filePath, lineNum, colNum));
568

Kai Koehne's avatar
Kai Koehne committed
569
    foreach (const ObjectReference &it, ref.children())
570 571 572
        buildDebugIdHashRecursive(it);
}

573
static QString buildIName(const QString &parentIname, int debugId)
574
{
575
    if (parentIname.isEmpty())
576 577
        return "inspect." + QString::number(debugId);
    return parentIname + "." + QString::number(debugId);
578 579
}

580
static QString buildIName(const QString &parentIname, const QString &name)
581
{
582
    return parentIname + "." + name;
583 584
}

585
void QmlInspectorAgent::addWatchData(const ObjectReference &obj,
586
                                     const QString &parentIname,
587
                                     bool append)
588
{
589
    qCDebug(qmlInspectorLog) << '(' << obj << parentIname << ')';
590
    QTC_ASSERT(m_qmlEngine, return);
591

592
    int objDebugId = obj.debugId();
593
    QString objIname = buildIName(parentIname, objDebugId);
594 595 596 597 598 599 600

    if (append) {
        QString name = obj.idString();
        if (name.isEmpty())
            name = obj.className();

        if (name.isEmpty())
601 602 603 604 605 606 607 608 609
            name = obj.name();

        if (name.isEmpty()) {
            FileReference file = obj.source();
            name = file.url().fileName() + ':' + QString::number(file.lineNumber());
        }

        if (name.isEmpty())
            name = tr("<anonymous>");
610 611

        // object
612 613 614
        auto objWatch = new WatchItem;
        objWatch->iname = objIname;
        objWatch->name = name;
615
        objWatch->id = objDebugId;
616 617 618
        objWatch->exp = name;
        objWatch->type = obj.className();
        objWatch->value = "object";
619 620
        objWatch->wantsChildren = true;

621
        m_qmlEngine->watchHandler()->insertItem(objWatch);
622
        addObjectWatch(objWatch->id);
623 624 625
        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
626
            const QString oldIname = m_debugIdToIname.value(objDebugId);
627
            if (oldIname != objIname)
628
                m_qmlEngine->watchHandler()->removeItemByIName(oldIname);
629
        }
630
        m_debugIdToIname.insert(objDebugId, objIname);
631 632
    }

633
    if (!m_qmlEngine->watchHandler()->isExpandedIName(objIname)) {
634 635
        // we don't know the children yet. Not adding the 'properties'
        // element makes sure we're queried on expansion.
636
        if (obj.needsMoreData())
637
            return;
638 639
    }

640
    // properties
641
    if (append && obj.properties().count()) {
642
        QString iname = objIname + ".[properties]";
643 644 645
        auto propertiesWatch = new WatchItem;
        propertiesWatch->iname = iname;
        propertiesWatch->name = tr("Properties");
646
        propertiesWatch->id = objDebugId;
647
        propertiesWatch->value = "list";
648
        propertiesWatch->wantsChildren = true;
649 650

        foreach (const PropertyReference &property, obj.properties()) {
651 652 653
            const QString propertyName = property.name();
            if (propertyName.isEmpty())
                continue;
654 655 656
            auto propertyWatch = new WatchItem;
            propertyWatch->iname = buildIName(iname, propertyName);
            propertyWatch->name = propertyName;
657
            propertyWatch->id = objDebugId;
658 659
            propertyWatch->exp = propertyName;
            propertyWatch->type = property.valueTypeName();
660 661
            propertyWatch->value = property.value().toString();
            propertyWatch->wantsChildren = false;
662
            propertiesWatch->appendChild(propertyWatch);
663
        }
664

665 666 667 668 669 670
        if (boolSetting(SortStructMembers)) {
            propertiesWatch->sortChildren([](const WatchItem *item1, const WatchItem *item2) {
                return item1->name < item2->name;
            });
        }

671
        m_qmlEngine->watchHandler()->insertItem(propertiesWatch);
672 673 674
    }

    // recurse
Kai Koehne's avatar
Kai Koehne committed
675
    foreach (const ObjectReference &child, obj.children())
676
        addWatchData(child, objIname, append);
677 678 679 680 681
}

void QmlInspectorAgent::log(QmlInspectorAgent::LogDirection direction,
                            const QString &message)
{
682
    QString msg = "Inspector";
683
    if (direction == LogSend)
684
        msg += " sending ";
685
    else
686
        msg += " receiving ";
687 688
    msg += message;

689 690
    if (m_qmlEngine)
        m_qmlEngine->showMessage(msg, LogDebug);
691 692
}

693
bool QmlInspectorAgent::isConnected() const
694
{
695
    return m_engineClient && m_engineClient->state() == QmlDebugClient::Enabled;
696 697
}

698 699
void QmlInspectorAgent::clearObjectTree()
{
700 701
    if (m_qmlEngine)
        m_qmlEngine->watchHandler()->removeAllData(true);
702
    m_objectTreeQueryIds.clear();
703
    m_fetchDataIds.clear();
704 705 706 707
    int old_count = m_debugIdHash.count();
    m_debugIdHash.clear();
    m_debugIdHash.reserve(old_count + 1);
    m_debugIdToIname.clear();
708
    m_debugIdToIname.insert(WatchItem::InvalidId, "inspect");
709
    m_objectStack.clear();
710 711 712 713 714 715 716 717 718
    m_objectWatches.clear();
}

void QmlInspectorAgent::clientStateChanged(QmlDebugClient::State state)
{
    QString serviceName;
    float version = 0;
    if (QmlDebugClient *client = qobject_cast<QmlDebugClient*>(sender())) {
        serviceName = client->name();
719
        version = client->serviceVersion();
720 721 722 723 724 725 726 727 728 729 730 731 732 733 734
    }

    m_qmlEngine->logServiceStateChange(serviceName, version, state);
}

void QmlInspectorAgent::toolsClientStateChanged(QmlDebugClient::State state)
{
    BaseToolsClient *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,
735
                m_qmlEngine.data(), &QmlEngine::logServiceActivity);
736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753
        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;
754
        enableTools(m_masterEngine->state() == InferiorRunOk);
755 756 757 758 759 760 761
        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,
762
                   m_qmlEngine.data(), &QmlEngine::logServiceActivity);
763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804

        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)
{
    BaseEngineDebugClient *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)
{
805
    QTC_ASSERT(m_toolsClient, return);
806
    if (checked) {
807 808
        m_toolsClient->setDesignModeBehavior(true);
        m_toolsClient->changeToSelectTool();
809 810
        m_zoomAction->setChecked(false);
    } else {
811
        m_toolsClient->setDesignModeBehavior(false);
812 813 814 815 816
    }
}

void QmlInspectorAgent::onZoomActionTriggered(bool checked)
{
817
    QTC_ASSERT(m_toolsClient, return);
818
    if (checked) {
819 820
        m_toolsClient->setDesignModeBehavior(true);
        m_toolsClient->changeToZoomTool();
821 822
        m_selectAction->setChecked(false);
    } else {
823
        m_toolsClient->setDesignModeBehavior(false);
824 825 826 827 828
    }
}

void QmlInspectorAgent::onShowAppOnTopChanged(bool checked)
{
829 830
    QTC_ASSERT(m_toolsClient, return);
    m_toolsClient->showAppOnTop(checked);
831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872
}

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)
{
    const QString fileName = m_masterEngine->toFileInProject(objSource.url());

    Core::EditorManager::openEditorAt(fileName, objSource.lineNumber());
873
    if (debugId != WatchItem::InvalidId && debugId != m_currentSelectedDebugId) {
874 875 876 877 878
        m_currentSelectedDebugId = debugId;
        m_currentSelectedDebugName = displayName(debugId);
    }
}

879
void QmlInspectorAgent::selectObject(const ObjectReference &obj, SelectionTarget target)
880 881
{
    if (m_toolsClient && target == ToolTarget)
882
        m_toolsClient->setObjectIdList(QList<ObjectReference>() << obj);
883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908

    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