qmlengine.cpp 16.4 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/

#include "qmlengine.h"
Lasse Holmstedt's avatar
Lasse Holmstedt committed
31
#include "qmladapter.h"
32

33
#include "debuggerconstants.h"
34
#include "debuggerplugin.h"
35
#include "debuggerdialogs.h"
36
#include "debuggerstringutils.h"
37
#include "debuggeruiswitcher.h"
38

39 40 41 42 43 44 45
#include "breakhandler.h"
#include "moduleshandler.h"
#include "registerhandler.h"
#include "stackhandler.h"
#include "watchhandler.h"
#include "watchutils.h"

Lasse Holmstedt's avatar
Lasse Holmstedt committed
46
#include <extensionsystem/pluginmanager.h>
47 48
#include <projectexplorer/environment.h>

49 50 51 52 53 54 55 56 57 58 59 60 61
#include <utils/qtcassert.h>

#include <QtCore/QDateTime>
#include <QtCore/QDebug>
#include <QtCore/QDir>
#include <QtCore/QFileInfo>
#include <QtCore/QTimer>

#include <QtGui/QAction>
#include <QtGui/QApplication>
#include <QtGui/QMainWindow>
#include <QtGui/QMessageBox>
#include <QtGui/QToolTip>
62
#include <QtGui/QTextDocument>
63 64

#include <QtNetwork/QTcpSocket>
65 66
#include <QtNetwork/QHostAddress>

67 68 69 70 71 72 73 74
#define DEBUG_QML 1
#if DEBUG_QML
#   define SDEBUG(s) qDebug() << s
#else
#   define SDEBUG(s)
#endif
# define XSDEBUG(s) qDebug() << s

Lasse Holmstedt's avatar
Lasse Holmstedt committed
75 76
enum {
    MaxConnectionAttempts = 50,
77
    ConnectionAttemptDefaultInterval = 200
Lasse Holmstedt's avatar
Lasse Holmstedt committed
78
};
79

80 81 82
namespace Debugger {
namespace Internal {

83 84 85 86 87 88
QDataStream& operator>>(QDataStream& s, WatchData &data)
{
    data = WatchData();
    QString value;
    QString type;
    bool hasChildren;
89
    s >> data.exp >> data.name >> value >> type >> hasChildren >> data.objectId;
90 91 92
    data.setType(type, false);
    data.setValue(value);
    data.setHasChildren(hasChildren);
93
    data.setAllUnneeded();
94 95 96
    return s;
}

97 98 99 100 101 102
///////////////////////////////////////////////////////////////////////
//
// QmlEngine
//
///////////////////////////////////////////////////////////////////////

103
QmlEngine::QmlEngine(const DebuggerStartParameters &startParameters)
Lasse Holmstedt's avatar
Lasse Holmstedt committed
104 105 106 107
    : DebuggerEngine(startParameters)
    , m_ping(0)
    , m_adapter(new QmlAdapter(this))
    , m_addedAdapterToObjectPool(false)
108
{
Lasse Holmstedt's avatar
Lasse Holmstedt committed
109

110 111 112 113 114 115
}

QmlEngine::~QmlEngine()
{
}

hjk's avatar
hjk committed
116
void QmlEngine::setupInferior()
117
{
hjk's avatar
hjk committed
118
    QTC_ASSERT(state() == InferiorSetupRequested, qDebug() << state());
Lasse Holmstedt's avatar
Lasse Holmstedt committed
119 120 121 122 123

    connect(&m_applicationLauncher, SIGNAL(processExited(int)), SLOT(disconnected()));
    m_applicationLauncher.setEnvironment(startParameters().environment);
    m_applicationLauncher.setWorkingDirectory(startParameters().workingDirectory);

hjk's avatar
hjk committed
124 125
    notifyInferiorSetupOk();
}
hjk's avatar
hjk committed
126

Lasse Holmstedt's avatar
Lasse Holmstedt committed
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
void QmlEngine::connectionEstablished()
{
    attemptBreakpointSynchronization();

    ExtensionSystem::PluginManager *pluginManager = ExtensionSystem::PluginManager::instance();
    pluginManager->addObject(m_adapter);
    m_addedAdapterToObjectPool = true;

    plugin()->showMessage(tr("QML Debugger connected."), StatusBar);

    notifyEngineRunAndInferiorRunOk();
}

void QmlEngine::connectionStartupFailed()
{
    QMessageBox::critical(0,
                          tr("Failed to connect to debugger"),
                          tr("Could not connect to debugger server.") );
    notifyEngineRunFailed();
}

void QmlEngine::connectionError()
{
    // do nothing for now - only exit the debugger when inferior exits.
}

hjk's avatar
hjk committed
153 154 155
void QmlEngine::runEngine()
{
    QTC_ASSERT(state() == EngineRunRequested, qDebug() << state());
Lasse Holmstedt's avatar
Lasse Holmstedt committed
156 157 158 159 160 161 162 163

    // ### TODO for non-qmlproject apps, start in a different way
    m_applicationLauncher.start(ProjectExplorer::ApplicationLauncher::Gui,
                                startParameters().executable,
                                startParameters().processArgs);

    m_adapter->beginConnection();
    plugin()->showMessage(tr("QML Debugger connecting..."), StatusBar);
164 165
}

hjk's avatar
hjk committed
166
void QmlEngine::shutdownInferior()
167
{
hjk's avatar
hjk committed
168
    QTC_ASSERT(state() == InferiorShutdownRequested, qDebug() << state());
Lasse Holmstedt's avatar
Lasse Holmstedt committed
169 170 171 172 173 174
    if (!m_applicationLauncher.isRunning()) {
        showMessage(tr("Trying to stop while process is no longer running."), LogError);
    } else {
        disconnect(&m_applicationLauncher, SIGNAL(processExited(int)), this, SLOT(disconnected()));
        m_applicationLauncher.stop();
    }
hjk's avatar
hjk committed
175 176 177 178 179 180
    notifyInferiorShutdownOk();
}

void QmlEngine::shutdownEngine()
{
    QTC_ASSERT(state() == EngineShutdownRequested, qDebug() << state());
Lasse Holmstedt's avatar
Lasse Holmstedt committed
181 182 183 184 185 186 187 188 189 190 191 192

    if (m_addedAdapterToObjectPool) {
        ExtensionSystem::PluginManager *pluginManager = ExtensionSystem::PluginManager::instance();
        pluginManager->removeObject(m_adapter);
    }

    if (m_applicationLauncher.isRunning()) {
        // should only happen if engine is ill
        disconnect(&m_applicationLauncher, SIGNAL(processExited(int)), this, SLOT(disconnected()));
        m_applicationLauncher.stop();
    }

193
    notifyEngineShutdownOk();
194 195
}

hjk's avatar
hjk committed
196
void QmlEngine::setupEngine()
197
{
Lasse Holmstedt's avatar
Lasse Holmstedt committed
198 199 200 201 202 203
    m_adapter->setMaxConnectionAttempts(MaxConnectionAttempts);
    m_adapter->setConnectionAttemptInterval(ConnectionAttemptDefaultInterval);
    connect(m_adapter, SIGNAL(connectionError()), SLOT(connectionError()));
    connect(m_adapter, SIGNAL(connected()), SLOT(connectionEstablished()));
    connect(m_adapter, SIGNAL(connectionStartupFailed()), SLOT(connectionStartupFailed()));

204
    notifyEngineSetupOk();
205 206
}

207 208
void QmlEngine::continueInferior()
{
hjk's avatar
hjk committed
209
    QTC_ASSERT(state() == InferiorStopOk, qDebug() << state());
210 211 212 213
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
    rs << QByteArray("CONTINUE");
    sendMessage(reply);
214
    resetLocation();
hjk's avatar
hjk committed
215 216
    notifyInferiorRunRequested();
    notifyInferiorRunOk();
217 218 219 220
}

void QmlEngine::interruptInferior()
{
221 222 223 224
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
    rs << QByteArray("INTERRUPT");
    sendMessage(reply);
225
    notifyInferiorStopOk();
226 227 228 229
}

void QmlEngine::executeStep()
{
230 231 232 233
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
    rs << QByteArray("STEPINTO");
    sendMessage(reply);
hjk's avatar
hjk committed
234 235
    notifyInferiorRunRequested();
    notifyInferiorRunOk();
236 237 238 239
}

void QmlEngine::executeStepI()
{
240 241 242 243
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
    rs << QByteArray("STEPINTO");
    sendMessage(reply);
hjk's avatar
hjk committed
244 245
    notifyInferiorRunRequested();
    notifyInferiorRunOk();
246 247 248 249
}

void QmlEngine::executeStepOut()
{
250 251 252 253
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
    rs << QByteArray("STEPOUT");
    sendMessage(reply);
hjk's avatar
hjk committed
254 255
    notifyInferiorRunRequested();
    notifyInferiorRunOk();
256 257 258 259
}

void QmlEngine::executeNext()
{
260 261 262 263
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
    rs << QByteArray("STEPOVER");
    sendMessage(reply);
hjk's avatar
hjk committed
264 265
    notifyInferiorRunRequested();
    notifyInferiorRunOk();
266 267 268 269
}

void QmlEngine::executeNextI()
{
270
    SDEBUG("QmlEngine::executeNextI()");
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295
}

void QmlEngine::executeRunToLine(const QString &fileName, int lineNumber)
{
    Q_UNUSED(fileName)
    Q_UNUSED(lineNumber)
    SDEBUG("FIXME:  QmlEngine::executeRunToLine()");
}

void QmlEngine::executeRunToFunction(const QString &functionName)
{
    Q_UNUSED(functionName)
    XSDEBUG("FIXME:  QmlEngine::executeRunToFunction()");
}

void QmlEngine::executeJumpToLine(const QString &fileName, int lineNumber)
{
    Q_UNUSED(fileName)
    Q_UNUSED(lineNumber)
    XSDEBUG("FIXME:  QmlEngine::executeJumpToLine()");
}

void QmlEngine::activateFrame(int index)
{
    Q_UNUSED(index)
296 297 298 299 300 301 302

    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
    rs << QByteArray("ACTIVATE_FRAME");
    rs << index;
    sendMessage(reply);

303
    gotoLocation(stackHandler()->frames().value(index), true);
304 305 306 307 308 309 310 311 312
}

void QmlEngine::selectThread(int index)
{
    Q_UNUSED(index)
}

void QmlEngine::attemptBreakpointSynchronization()
{
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328
    BreakHandler *handler = breakHandler();
    //bool updateNeeded = false;
    QSet< QPair<QString, qint32> > breakList;
    for (int index = 0; index != handler->size(); ++index) {
        BreakpointData *data = handler->at(index);
        breakList << qMakePair(data->fileName, data->lineNumber.toInt());
    }

    {
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
    rs << QByteArray("BREAKPOINTS");
    rs << breakList;
    //qDebug() << Q_FUNC_INFO << breakList;
    sendMessage(reply);
    }
329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374
}

void QmlEngine::loadSymbols(const QString &moduleName)
{
    Q_UNUSED(moduleName)
}

void QmlEngine::loadAllSymbols()
{
}

void QmlEngine::reloadModules()
{
}

void QmlEngine::requestModuleSymbols(const QString &moduleName)
{
    Q_UNUSED(moduleName)
}

//////////////////////////////////////////////////////////////////////
//
// Tooltip specific stuff
//
//////////////////////////////////////////////////////////////////////

static WatchData m_toolTip;
static QPoint m_toolTipPos;
static QHash<QString, WatchData> m_toolTipCache;

void QmlEngine::setToolTipExpression(const QPoint &mousePos, TextEditor::ITextEditor *editor, int cursorPos)
{
    Q_UNUSED(mousePos)
    Q_UNUSED(editor)
    Q_UNUSED(cursorPos)
}

//////////////////////////////////////////////////////////////////////
//
// Watch specific stuff
//
//////////////////////////////////////////////////////////////////////

void QmlEngine::assignValueInDebugger(const QString &expression,
    const QString &value)
{
375 376 377 378 379 380 381 382 383 384 385 386 387
    QRegExp inObject("@([0-9a-fA-F]+)->(.+)");
    if (inObject.exactMatch(expression)) {
        bool ok = false;
        quint64 objectId = inObject.cap(1).toULongLong(&ok, 16);
        QString property = inObject.cap(2);
        if (ok && objectId > 0 && !property.isEmpty()) {
            QByteArray reply;
            QDataStream rs(&reply, QIODevice::WriteOnly);
            rs << QByteArray("SET_PROPERTY");
            rs << expression.toUtf8() << objectId << property << value;
            sendMessage(reply);
        }
    }
388 389
}

390
void QmlEngine::updateWatchData(const WatchData &data)
391
{
Olivier Goffart's avatar
Olivier Goffart committed
392
//    qDebug() << "UPDATE WATCH DATA" << data.toString();
393
    //watchHandler()->rebuildModel();
394
    showStatusMessage(tr("Stopped."), 5000);
395

396
    if (!data.name.isEmpty() && data.isValueNeeded()) {
397 398 399 400 401 402 403
        QByteArray reply;
        QDataStream rs(&reply, QIODevice::WriteOnly);
        rs << QByteArray("EXEC");
        rs << data.iname << data.name;
        sendMessage(reply);
    }

404 405
    if (!data.name.isEmpty() && data.isChildrenNeeded()
            && watchHandler()->isExpandedIName(data.iname))
406 407
        expandObject(data.iname, data.objectId);

408 409 410 411 412 413 414
    {
        QByteArray reply;
        QDataStream rs(&reply, QIODevice::WriteOnly);
        rs << QByteArray("WATCH_EXPRESSIONS");
        rs << watchHandler()->watchedExpressions();
        sendMessage(reply);
    }
415 416 417

    if (!data.isSomethingNeeded())
        watchHandler()->insertData(data);
418 419
}

420 421 422 423 424 425 426 427 428
void QmlEngine::expandObject(const QByteArray& iname, quint64 objectId)
{
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
    rs << QByteArray("EXPAND");
    rs << iname << objectId;
    sendMessage(reply);
}

429 430 431 432 433 434 435 436 437 438
void QmlEngine::sendPing()
{
    m_ping++;
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
    rs << QByteArray("PING");
    rs << m_ping;
    sendMessage(reply);
}

439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455
DebuggerEngine *createQmlEngine(const DebuggerStartParameters &sp)
{
    return new QmlEngine(sp);
}

unsigned QmlEngine::debuggerCapabilities() const
{
    return AddWatcherCapability;
    /*ReverseSteppingCapability | SnapshotCapability
        | AutoDerefPointersCapability | DisassemblerCapability
        | RegisterCapability | ShowMemoryCapability
        | JumpToLineCapability | ReloadModuleCapability
        | ReloadModuleSymbolsCapability | BreakOnThrowAndCatchCapability
        | ReturnFromFunctionCapability
        | CreateFullBacktraceCapability
        | WatchpointCapability
        | AddWatcherCapability;*/
456 457 458 459 460 461 462 463 464 465
}

void QmlEngine::messageReceived(const QByteArray &message)
{
    QByteArray rwData = message;
    QDataStream stream(&rwData, QIODevice::ReadOnly);

    QByteArray command;
    stream >> command;

466
    showMessage(_("RECEIVED RESPONSE: ") + quoteUnprintableLatin1(message));
467
    if (command == "STOPPED") {
hjk's avatar
hjk committed
468
        notifyInferiorSpontaneousStop();
469

470
        QList<QPair<QString, QPair<QString, qint32> > > backtrace;
471 472
        QList<WatchData> watches;
        QList<WatchData> locals;
473
        stream >> backtrace >> watches >> locals;
474

475
        StackFrames stackFrames;
476 477 478 479 480 481
        typedef QPair<QString, QPair<QString, qint32> > Iterator;
        foreach (const Iterator &it, backtrace) {
            StackFrame frame;
            frame.file = it.second.first;
            frame.line = it.second.second;
            frame.function = it.first;
482
            stackFrames.append(frame);
483 484 485 486 487 488
        }

        gotoLocation(stackFrames.value(0), true);
        stackHandler()->setFrames(stackFrames);

        watchHandler()->beginCycle();
489
        bool needPing = false;
490

491
        foreach (WatchData data, watches) {
492
            data.iname = watchHandler()->watcherName(data.exp);
493
            watchHandler()->insertData(data);
494

495 496
            if (watchHandler()->expandedINames().contains(data.iname)) {
                needPing = true;
497
                expandObject(data.iname, data.objectId);
498
            }
499 500
        }

501 502 503
        foreach (WatchData data, locals) {
            data.iname = "local." + data.exp;
            watchHandler()->insertData(data);
504

505 506
            if (watchHandler()->expandedINames().contains(data.iname)) {
                needPing = true;
507
                expandObject(data.iname, data.objectId);
508
            }
hjk's avatar
hjk committed
509 510
        }

511 512 513 514
        if (needPing)
            sendPing();
        else
            watchHandler()->endCycle();
515

516 517 518
        // Ensure we got the right ui right now.
        Debugger::DebuggerUISwitcher *uiSwitcher
            = Debugger::DebuggerUISwitcher::instance();
519 520
        uiSwitcher->setActiveLanguage("C++");

521 522 523 524 525 526
        bool becauseOfexception;
        stream >> becauseOfexception;
        if (becauseOfexception) {
            QString error;
            stream >> error;

527 528 529
            QString msg =
                tr("<p>An Uncaught Exception occured in <i>%1</i>:</p><p>%2</p>")
                    .arg(stackFrames.value(0).file, Qt::escape(error));
530 531 532
            showMessageBox(QMessageBox::Information, tr("Uncaught Exception"), msg);
        }

533

534 535
    } else if (command == "RESULT") {
        WatchData data;
536 537 538
        QByteArray iname;
        stream >> iname >> data;
        data.iname = iname;
539
        watchHandler()->insertData(data);
540 541 542 543 544

    } else if (command == "EXPANDED") {
        QList<WatchData> result;
        QByteArray iname;
        stream >> iname >> result;
545
        bool needPing = false;
546 547 548 549
        foreach (WatchData data, result) {
            data.iname = iname + '.' + data.exp;
            watchHandler()->insertData(data);

550 551
            if (watchHandler()->expandedINames().contains(data.iname)) {
                needPing = true;
552
                expandObject(data.iname, data.objectId);
553
            }
554
        }
555 556
        if (needPing)
            sendPing();
557 558 559 560 561
    } else if (command == "LOCALS") {
        QList<WatchData> locals;
        int frameId;
        stream >> frameId >> locals;
        watchHandler()->beginCycle();
562
        bool needPing = false;
563 564 565
        foreach (WatchData data, locals) {
            data.iname = "local." + data.exp;
            watchHandler()->insertData(data);
566 567
            if (watchHandler()->expandedINames().contains(data.iname)) {
                needPing = true;
568
                expandObject(data.iname, data.objectId);
569
            }
570
        }
571 572 573 574 575 576 577 578 579 580
        if (needPing)
            sendPing();
        else
            watchHandler()->endCycle();

    } else if (command == "PONG") {
        int ping;
        stream >> ping;
        if (ping == m_ping)
            watchHandler()->endCycle();
581 582 583 584 585 586
    } else {
        qDebug() << Q_FUNC_INFO << "Unknown command: " << command;
    }

}

587 588
void QmlEngine::disconnected()
{
Lasse Holmstedt's avatar
Lasse Holmstedt committed
589
    plugin()->showMessage(tr("QML Debugger disconnected."), StatusBar);
590 591 592 593
    notifyInferiorExited();
}


594 595
} // namespace Internal
} // namespace Debugger
596