qmlinspectoragent.cpp 32 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
2
**
Eike Ziller's avatar
Eike Ziller committed
3 4
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://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
Eike Ziller's avatar
Eike Ziller committed
12 13
** a written agreement between you and The Qt Company.  For licensing terms and
** conditions see http://www.qt.io/terms-conditions.  For further information
Eike Ziller's avatar
Eike Ziller committed
14
** use the contact form at http://www.qt.io/contact-us.
15 16
**
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17
** Alternatively, this file may be used under the terms of the GNU Lesser
Eike Ziller's avatar
Eike Ziller committed
18 19 20 21 22 23
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file.  Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
hjk's avatar
hjk committed
24
**
Eike Ziller's avatar
Eike Ziller committed
25 26
** In addition, as a special exception, The Qt Company gives you certain additional
** rights.  These rights are described in The Qt Company LGPL Exception
27 28
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
29
****************************************************************************/
30 31

#include "qmlinspectoragent.h"
32
#include "qmlengine.h"
33

34 35 36 37 38
#include <debugger/debuggeractions.h>
#include <debugger/debuggercore.h>
#include <debugger/debuggerengine.h>
#include <debugger/debuggerstringutils.h>
#include <debugger/watchhandler.h>
39

40 41 42 43 44 45 46 47 48
#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>
49
#include <qmldebug/qmldebugconstants.h>
50 51 52
#include <qmldebug/qmlenginedebugclient.h>
#include <qmldebug/qmltoolsclient.h>

53
#include <utils/fileutils.h>
54 55
#include <utils/qtcassert.h>
#include <utils/savedaction.h>
56

57
#include <QElapsedTimer>
58
#include <QFileInfo>
59
#include <QLoggingCategory>
60

Kai Koehne's avatar
Kai Koehne committed
61
using namespace QmlDebug;
62
using namespace QmlDebug::Constants;
Kai Koehne's avatar
Kai Koehne committed
63

64 65 66
namespace Debugger {
namespace Internal {

67
Q_LOGGING_CATEGORY(qmlInspectorLog, "qtc.dbg.qmlinspector")
68 69 70 71

/*!
 * DebuggerAgent updates the watchhandler with the object tree data.
 */
72 73
QmlInspectorAgent::QmlInspectorAgent(QmlEngine *engine, QmlDebugConnection *connection)
    : m_qmlEngine(engine)
74 75 76 77
    , m_engineClient(0)
    , m_engineQueryId(0)
    , m_rootContextQueryId(0)
    , m_objectToSelect(-1)
78 79 80 81 82 83 84 85 86 87 88
    , m_masterEngine(engine)
    , m_toolsClient(0)
    , m_targetToSync(NoTarget)
    , m_debugIdToSelect(-1)
    , m_currentSelectedDebugId(-1)
    , m_toolsClientConnected(false)
    , m_inspectorToolsContext("Debugger.QmlInspector")
    , m_selectAction(new QAction(this))
    , m_zoomAction(new QAction(this))
    , m_showAppOnTopAction(action(ShowAppOnTop))
    , m_engineClientConnected(false)
89
{
90
    m_debugIdToIname.insert(-1, QByteArray("inspect"));
hjk's avatar
hjk committed
91
    connect(action(ShowQmlObjectTree),
92
            &Utils::SavedAction::valueChanged, this, &QmlInspectorAgent::updateState);
93
    m_delayQueryTimer.setSingleShot(true);
94
    m_delayQueryTimer.setInterval(100);
Montel Laurent's avatar
Montel Laurent committed
95 96
    connect(&m_delayQueryTimer, &QTimer::timeout,
            this, &QmlInspectorAgent::queryEngineContext);
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 152 153 154 155 156 157 158 159

    if (!m_masterEngine->isMasterEngine())
        m_masterEngine = m_masterEngine->masterEngine();
    connect(m_masterEngine, &DebuggerEngine::stateChanged,
            this, &QmlInspectorAgent::onEngineStateChanged);

    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);
160 161 162 163 164
}

quint32 QmlInspectorAgent::queryExpressionResult(int debugId,
                                                 const QString &expression)
{
165 166 167
    if (!m_engineClient)
        return 0;

168 169 170
    qCDebug(qmlInspectorLog)
            << __FUNCTION__ << '(' << debugId << expression
            << m_engine.debugId() << ')';
171 172

    return m_engineClient->queryExpressionResult(debugId, expression,
173
                                                 m_engine.debugId());
174 175
}

176 177 178
void QmlInspectorAgent::assignValue(const WatchData *data,
                                    const QString &expr, const QVariant &valueV)
{
179 180
    qCDebug(qmlInspectorLog)
            << __FUNCTION__ << '(' << data->id << ')' << data->iname;
181 182 183 184 185 186 187 188 189 190 191 192

    if (data->id) {
        QString val(valueV.toString());
        if (valueV.type() == QVariant::String) {
            val = val.replace(QLatin1Char('\"'), QLatin1String("\\\""));
            val = QLatin1Char('\"') + val + QLatin1Char('\"');
        }
        QString expression = QString(_("%1 = %2;")).arg(expr).arg(val);
        queryExpressionResult(data->id, expression);
    }
}

193
static int parentIdForIname(const QByteArray &iname)
194 195 196 197 198 199 200 201 202 203
{
    // Extract the parent id
    int lastIndex = iname.lastIndexOf('.');
    int secondLastIndex = iname.lastIndexOf('.', lastIndex - 1);
    int parentId = -1;
    if (secondLastIndex != -1)
        parentId = iname.mid(secondLastIndex + 1, lastIndex - secondLastIndex - 1).toInt();
    return parentId;
}

204 205
void QmlInspectorAgent::updateWatchData(const WatchData &data)
{
206
    qCDebug(qmlInspectorLog) << __FUNCTION__ << '(' << data.id << ')';
207

hjk's avatar
hjk committed
208
    if (data.id && !m_fetchDataIds.contains(data.id)) {
209
        // objects
hjk's avatar
hjk committed
210
        m_fetchDataIds << data.id;
211
        fetchObject(data.id);
212 213 214
    }
}

215
void QmlInspectorAgent::watchDataSelected(quint64 id)
216
{
217
    qCDebug(qmlInspectorLog) << __FUNCTION__ << '(' << id << ')';
218

219 220
    if (id) {
        QTC_ASSERT(m_debugIdLocations.keys().contains(id), return);
221
        jumpToObjectDefinitionInEditor(m_debugIdLocations.value(id), id);
222 223 224
    }
}

225
bool QmlInspectorAgent::selectObjectInTree(int debugId)
226
{
227 228 229
    qCDebug(qmlInspectorLog) << __FUNCTION__ << '(' << debugId << ')' << endl
                             << "  " << debugId << "already fetched? "
                             << m_debugIdToIname.contains(debugId);
230 231 232 233

    if (m_debugIdToIname.contains(debugId)) {
        QByteArray iname = m_debugIdToIname.value(debugId);
        QTC_ASSERT(iname.startsWith("inspect."), qDebug() << iname);
234
        qCDebug(qmlInspectorLog) << "  selecting" << iname << "in tree";
235
        m_qmlEngine->watchHandler()->setCurrentItem(iname);
236
        m_objectToSelect = 0;
237
        return true;
238
    } else {
239
        // we may have to fetch it
240
        m_objectToSelect = debugId;
241 242 243
        using namespace QmlDebug::Constants;
        if (m_engineClient->objectName() == QLatin1String(QDECLARATIVE_ENGINE)) {
            // reset current Selection
244 245
            QByteArray root = m_qmlEngine->watchHandler()->watchItem(QModelIndex())->iname;
            m_qmlEngine->watchHandler()->setCurrentItem(root);
246 247 248
        } else {
            fetchObject(debugId);
        }
249
        return false;
250 251 252
    }
}

253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282
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, -1,
                           FileReference(QUrl::fromLocalFile(file), line, column));
}

283
void QmlInspectorAgent::addObjectWatch(int objectDebugId)
284
{
285
    qCDebug(qmlInspectorLog) << __FUNCTION__ << '(' << objectDebugId << ')';
286 287

    if (objectDebugId == -1)
288
        return;
289

hjk's avatar
hjk committed
290
    if (!isConnected() || !boolSetting(ShowQmlObjectTree))
291
        return;
292 293 294

    // already set
    if (m_objectWatches.contains(objectDebugId))
295
        return;
296 297

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

300
    if (m_engineClient->addWatch(objectDebugId))
301 302 303
        m_objectWatches.append(objectDebugId);
}

304 305
QString QmlInspectorAgent::displayName(int objectDebugId) const
{
hjk's avatar
hjk committed
306
    if (!isConnected() || !boolSetting(ShowQmlObjectTree))
307 308 309
        return QString();

    if (m_debugIdToIname.contains(objectDebugId)) {
310
        const WatchItem *item = m_qmlEngine->watchHandler()->findItem(
311
                    m_debugIdToIname.value(objectDebugId));
312 313
        QTC_ASSERT(item, return QString());
        return item->name;
314 315 316 317
    }
    return QString();
}

318
void QmlInspectorAgent::updateState()
319 320
{
    if (m_engineClient
321
            && (m_engineClient->state() == QmlDebugClient::Enabled)
hjk's avatar
hjk committed
322
            && boolSetting(ShowQmlObjectTree)) {
323 324
        reloadEngines();
    } else {
325
        clearObjectTree();
326 327 328 329 330 331
    }
}

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

334
    if (type == "FETCH_OBJECT_R") {
335
        log(LogReceive, _("FETCH_OBJECT_R %1").arg(
Kai Koehne's avatar
Kai Koehne committed
336
                qvariant_cast<ObjectReference>(value).idString()));
337 338 339
    } else if (type == "SET_BINDING_R"
               || type == "RESET_BINDING_R"
               || type == "SET_METHOD_BODY_R") {
340
        // FIXME: This is not supported anymore.
341 342
        QString msg = QLatin1String(type) + tr("Success:");
        msg += QLatin1Char(' ');
343
        msg += value.toBool() ? QLatin1Char('1') : QLatin1Char('0');
344 345
        // if (!value.toBool())
        //     emit automaticUpdateFailed();
346
        log(LogReceive, msg);
347 348 349 350 351
    } else {
        log(LogReceive, QLatin1String(type));
    }

    if (m_objectTreeQueryIds.contains(queryId)) {
352
        m_objectTreeQueryIds.removeOne(queryId);
353 354
        if (value.type() == QVariant::List) {
            QVariantList objList = value.toList();
355
            foreach (const QVariant &var, objList) {
356 357
                // TODO: check which among the list is the actual
                // object that needs to be selected.
358
                verifyAndInsertObjectInTree(qvariant_cast<ObjectReference>(var));
359 360
            }
        } else {
361
            verifyAndInsertObjectInTree(qvariant_cast<ObjectReference>(value));
362
        }
363 364 365 366 367 368 369 370 371 372
    } 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();
373
        updateObjectTree(qvariant_cast<ContextReference>(value));
374
    } else {
375
        m_qmlEngine->expressionEvaluated(queryId, value);
376 377
    }

378
    qCDebug(qmlInspectorLog) << __FUNCTION__ << "done";
379 380
}

381
void QmlInspectorAgent::newObject(int engineId, int /*objectId*/, int /*parentId*/)
382
{
383
    qCDebug(qmlInspectorLog) << __FUNCTION__ << "()";
384

385
    log(LogReceive, QLatin1String("OBJECT_CREATED"));
386 387 388

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

390 391
    // TODO: FIX THIS for qt 5.x (Needs update in the qt side)
    m_delayQueryTimer.start();
392 393
}

394 395 396 397 398
void QmlInspectorAgent::onValueChanged(int debugId, const QByteArray &propertyName,
                                       const QVariant &value)
{
    const QByteArray iname = m_debugIdToIname.value(debugId) +
            ".[properties]." + propertyName;
399
    WatchHandler *watchHandler = m_qmlEngine->watchHandler();
400 401 402
    qCDebug(qmlInspectorLog)
            << __FUNCTION__ << '(' << debugId << ')' << iname
            << value.toString();
403 404 405
    if (WatchItem *item = watchHandler->findItem(iname)) {
        item->value = value.toString();
        item->update();
406 407 408
    }
}

409 410
void QmlInspectorAgent::reloadEngines()
{
411
    qCDebug(qmlInspectorLog) << __FUNCTION__ << "()";
412 413 414 415 416 417 418 419 420

    if (!isConnected())
        return;

    log(LogSend, _("LIST_ENGINES"));

    m_engineQueryId = m_engineClient->queryAvailableEngines();
}

421
void QmlInspectorAgent::queryEngineContext()
422
{
423
    qCDebug(qmlInspectorLog) << __FUNCTION__;
424

hjk's avatar
hjk committed
425
    if (!isConnected() || !boolSetting(ShowQmlObjectTree))
426 427
        return;

428
    log(LogSend, QLatin1String("LIST_OBJECTS"));
429 430

    m_rootContextQueryId
431
            = m_engineClient->queryRootContexts(m_engine);
432 433
}

434
void QmlInspectorAgent::fetchObject(int debugId)
435
{
436
    qCDebug(qmlInspectorLog) << __FUNCTION__ << '(' << debugId << ')';
437

hjk's avatar
hjk committed
438
    if (!isConnected() || !boolSetting(ShowQmlObjectTree))
439
        return;
440

441
    log(LogSend, QLatin1String("FETCH_OBJECT ") + QString::number(debugId));
442
    quint32 queryId = m_engineClient->queryObject(debugId);
443 444
    qCDebug(qmlInspectorLog) << __FUNCTION__ << '(' << debugId << ')'
                             << " - query id" << queryId;
445
    m_objectTreeQueryIds << queryId;
446 447
}

448
void QmlInspectorAgent::updateObjectTree(const ContextReference &context)
449
{
450
    qCDebug(qmlInspectorLog) << __FUNCTION__ << '(' << context << ')';
451

hjk's avatar
hjk committed
452
    if (!isConnected() || !boolSetting(ShowQmlObjectTree))
453 454
        return;

455
    foreach (const ObjectReference & obj, context.objects())
456
        verifyAndInsertObjectInTree(obj);
457

Kai Koehne's avatar
Kai Koehne committed
458
    foreach (const ContextReference &child, context.contexts())
459
        updateObjectTree(child);
460 461
}

462
void QmlInspectorAgent::verifyAndInsertObjectInTree(const ObjectReference &object)
463
{
464
    qCDebug(qmlInspectorLog) << __FUNCTION__ << '(' << object << ')';
465

466 467
    if (!object.isValid())
        return;
468

469 470 471 472 473 474 475
    // 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 == -1.
    // 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.

476
    WatchHandler *handler = m_qmlEngine->watchHandler();
477 478 479 480 481 482
    const int parentId = object.parentId();
    const int objectDebugId = object.debugId();
    if (m_debugIdToIname.contains(parentId)) {
        QByteArray parentIname = m_debugIdToIname.value(parentId);
        if (parentId != -1 && !handler->isExpandedIName(parentIname)) {
            m_objectStack.push(object);
483
            handler->fetchMore(parentIname);
484 485 486
            return; // recursive
        }
        insertObjectInTree(object);
487

488 489 490 491 492 493 494 495 496 497 498 499
    } 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)) {
            QByteArray objectIname = m_debugIdToIname.value(objectDebugId);
            if (!handler->isExpandedIName(objectIname)) {
500
                handler->fetchMore(objectIname);
501 502 503 504 505 506 507 508 509 510
            } else {
                verifyAndInsertObjectInTree(m_objectStack.pop());
                return; // recursive
            }
        }
    }
}

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

513 514
    const int objectDebugId = object.debugId();
    const int parentId = parentIdForIname(m_debugIdToIname.value(objectDebugId));
515

516
    QElapsedTimer timeElapsed;
517 518 519

    bool printTime = qmlInspectorLog().isDebugEnabled();
    if (printTime)
520
        timeElapsed.start();
521
    addWatchData(object, m_debugIdToIname.value(parentId), true);
522 523 524
    qCDebug(qmlInspectorLog) << __FUNCTION__ << "Time: Build Watch Data took "
                             << timeElapsed.elapsed() << " ms";
    if (printTime)
525 526
        timeElapsed.start();
    buildDebugIdHashRecursive(object);
527 528
    qCDebug(qmlInspectorLog) << __FUNCTION__ << "Time: Build Debug Id Hash took "
                             << timeElapsed.elapsed() << " ms";
529

530
    if (printTime)
531
        timeElapsed.start();
532 533
    qCDebug(qmlInspectorLog) << __FUNCTION__ << "Time: Insertion took "
                             << timeElapsed.elapsed() << " ms";
534

535 536 537 538
    if (object.debugId() == m_debugIdToSelect) {
        m_debugIdToSelect = -1;
        selectObject(object, m_targetToSync);
    }
539

540
    if (m_debugIdToIname.contains(m_objectToSelect)) {
541
        // select item in view
542
        QByteArray iname = m_debugIdToIname.value(m_objectToSelect);
543
        qCDebug(qmlInspectorLog) << "  selecting" << iname << "in tree";
544
        m_qmlEngine->watchHandler()->setCurrentItem(iname);
545
        m_objectToSelect = -1;
546
    }
547 548
    m_qmlEngine->watchHandler()->updateWatchersWindow();
    m_qmlEngine->watchHandler()->reexpandItems();
549 550
}

Kai Koehne's avatar
Kai Koehne committed
551
void QmlInspectorAgent::buildDebugIdHashRecursive(const ObjectReference &ref)
552
{
553
    qCDebug(qmlInspectorLog) << __FUNCTION__ << '(' << ref << ')';
554 555 556 557 558 559 560 561

    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)
562
    static QRegExp rx(QLatin1String("(.*)_(\\d+):(\\d+)$"));
563 564 565 566 567 568 569
    if (rx.exactMatch(fileUrl.path())) {
        fileUrl.setPath(rx.cap(1));
        rev = rx.cap(2).toInt();
        lineNum += rx.cap(3).toInt() - 1;
    }

    const QString filePath
570
            = m_qmlEngine->toFileInProject(fileUrl);
571 572 573 574 575 576

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

Kai Koehne's avatar
Kai Koehne committed
579
    foreach (const ObjectReference &it, ref.children())
580 581 582
        buildDebugIdHashRecursive(it);
}

583
static QByteArray buildIName(const QByteArray &parentIname, int debugId)
584
{
585
    if (parentIname.isEmpty())
586
        return "inspect." + QByteArray::number(debugId);
587
    return parentIname + "." + QByteArray::number(debugId);
588 589
}

590
static QByteArray buildIName(const QByteArray &parentIname, const QString &name)
591
{
592
    return parentIname + "." + name.toLatin1();
593 594
}

595 596 597
void QmlInspectorAgent::addWatchData(const ObjectReference &obj,
                                     const QByteArray &parentIname,
                                     bool append)
598
{
599
    qCDebug(qmlInspectorLog) << '(' << obj << parentIname << ')';
600

601 602 603 604 605 606 607 608 609
    int objDebugId = obj.debugId();
    QByteArray objIname = buildIName(parentIname, objDebugId);

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

        if (name.isEmpty())
610
            return;
611 612

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

621
        m_qmlEngine->watchHandler()->insertItem(objWatch);
622
        addObjectWatch(objWatch->id);
623 624 625 626 627
        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 QByteArray oldIname = m_debugIdToIname.value(objDebugId);
            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.
Aurindam Jana's avatar
Aurindam Jana committed
636
        if (obj.needsMoreData())
637
            return;
638 639
    }

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

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

663
        m_qmlEngine->watchHandler()->insertItem(propertiesWatch);
664 665 666
    }

    // recurse
Kai Koehne's avatar
Kai Koehne committed
667
    foreach (const ObjectReference &child, obj.children())
668
        addWatchData(child, objIname, append);
669 670 671 672 673 674 675 676 677 678 679 680
}

void QmlInspectorAgent::log(QmlInspectorAgent::LogDirection direction,
                            const QString &message)
{
    QString msg = _("Inspector");
    if (direction == LogSend)
        msg += _(" sending ");
    else
        msg += _(" receiving ");
    msg += message;

681 682
    if (m_qmlEngine)
        m_qmlEngine->showMessage(msg, LogDebug);
683 684
}

685
bool QmlInspectorAgent::isConnected() const
686
{
687
    return m_engineClient && m_engineClient->state() == QmlDebugClient::Enabled;
688 689
}

690 691
void QmlInspectorAgent::clearObjectTree()
{
692
    m_qmlEngine->watchHandler()->removeAllData(true);
693
    m_objectTreeQueryIds.clear();
694
    m_fetchDataIds.clear();
695 696 697 698
    int old_count = m_debugIdHash.count();
    m_debugIdHash.clear();
    m_debugIdHash.reserve(old_count + 1);
    m_debugIdToIname.clear();
699
    m_debugIdToIname.insert(-1, QByteArray("inspect"));
700
    m_objectStack.clear();
701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 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 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 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 873 874 875 876 877 878 879 880 881 882 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
    m_objectWatches.clear();
}

BaseToolsClient *QmlInspectorAgent::toolsClient() const
{
    return m_toolsClient;
}

void QmlInspectorAgent::clientStateChanged(QmlDebugClient::State state)
{
    QString serviceName;
    float version = 0;
    if (QmlDebugClient *client = qobject_cast<QmlDebugClient*>(sender())) {
        serviceName = client->name();
        version = client->remoteVersion();
    }

    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,
                m_qmlEngine, &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;
        onEngineStateChanged(m_masterEngine->state());
        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, &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)
{
    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)
{
    QTC_ASSERT(toolsClient(), return);
    if (checked) {
        toolsClient()->setDesignModeBehavior(true);
        toolsClient()->changeToSelectTool();
        m_zoomAction->setChecked(false);
    } else {
        toolsClient()->setDesignModeBehavior(false);
    }
}

void QmlInspectorAgent::onZoomActionTriggered(bool checked)
{
    QTC_ASSERT(toolsClient(), return);
    if (checked) {
        toolsClient()->setDesignModeBehavior(true);
        toolsClient()->changeToZoomTool();
        m_selectAction->setChecked(false);
    } else {
        toolsClient()->setDesignModeBehavior(false);
    }
}

void QmlInspectorAgent::onShowAppOnTopChanged(bool checked)
{
    QTC_ASSERT(toolsClient(), return);
    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)
{
    const QString fileName = m_masterEngine->toFileInProject(objSource.url());

    Core::EditorManager::openEditorAt(fileName, objSource.lineNumber());
    if (debugId != -1 && 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();
}

void QmlInspectorAgent::onEngineStateChanged(const DebuggerState state)
{
    enableTools(state == InferiorRunOk);
908
}
909

910 911
} // namespace Internal
} // namespace Debugger