qmlengine.cpp 32.4 KB
Newer Older
1 2 3 4 5 6 7 8
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
con's avatar
con committed
9
** No Commercial Usage
10
**
con's avatar
con committed
11 12 13 14
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
15 16 17 18 19 20 21 22 23 24
**
** 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.
**
con's avatar
con committed
25 26 27 28 29 30
** In addition, as a special exception, Nokia gives you certain additional
** rights.  These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
31 32 33 34
**
**************************************************************************/

#include "qmlengine.h"
Lasse Holmstedt's avatar
Lasse Holmstedt committed
35
#include "qmladapter.h"
36

Friedemann Kleint's avatar
Friedemann Kleint committed
37
#include "debuggerstartparameters.h"
38
#include "debuggeractions.h"
39
#include "debuggerconstants.h"
hjk's avatar
hjk committed
40
#include "debuggercore.h"
41
#include "debuggerdialogs.h"
42
#include "debuggermainwindow.h"
hjk's avatar
hjk committed
43
#include "debuggerrunner.h"
44
#include "debuggerstringutils.h"
45
#include "debuggertooltipmanager.h"
46

47 48 49 50 51 52 53
#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
54
#include <extensionsystem/pluginmanager.h>
55
#include <projectexplorer/applicationlauncher.h>
56

57
#include <utils/environment.h>
58 59
#include <utils/qtcassert.h>

60
#include <coreplugin/icore.h>
61 62
#include <coreplugin/helpmanager.h>

63 64 65 66 67 68 69 70 71 72 73
#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>
74
#include <QtGui/QTextDocument>
75 76

#include <QtNetwork/QTcpSocket>
77 78
#include <QtNetwork/QHostAddress>

79 80 81 82 83 84 85 86
#define DEBUG_QML 1
#if DEBUG_QML
#   define SDEBUG(s) qDebug() << s
#else
#   define SDEBUG(s)
#endif
# define XSDEBUG(s) qDebug() << s

hjk's avatar
hjk committed
87 88
using namespace ProjectExplorer;

89 90 91
namespace Debugger {
namespace Internal {

92 93 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
struct JSAgentBreakpointData
{
    QByteArray functionName;
    QByteArray fileName;
    qint32 lineNumber;
};

uint qHash(const JSAgentBreakpointData &b)
{
    return b.lineNumber ^ qHash(b.fileName);
}

QDataStream &operator<<(QDataStream &s, const JSAgentBreakpointData &data)
{
    return s << data.functionName << data.fileName << data.lineNumber;
}

QDataStream &operator>>(QDataStream &s, JSAgentBreakpointData &data)
{
    return s >> data.functionName >> data.fileName >> data.lineNumber;
}

bool operator==(const JSAgentBreakpointData &b1, const JSAgentBreakpointData &b2)
{
    return b1.lineNumber == b2.lineNumber && b1.fileName == b2.fileName;
}

typedef QSet<JSAgentBreakpointData> JSAgentBreakpoints;


122
static QDataStream &operator>>(QDataStream &s, WatchData &data)
123 124
{
    data = WatchData();
125 126 127 128 129 130 131 132
    QByteArray name;
    QByteArray value;
    QByteArray type;
    bool hasChildren = false;
    s >> data.exp >> name >> value >> type >> hasChildren >> data.id;
    data.name = QString::fromUtf8(name);
    data.setType(type, false);
    data.setValue(QString::fromUtf8(value));
133
    data.setHasChildren(hasChildren);
134
    data.setAllUnneeded();
135 136 137
    return s;
}

138
static QDataStream &operator>>(QDataStream &s, StackFrame &frame)
139 140 141 142 143 144 145
{
    frame = StackFrame();
    QByteArray function;
    QByteArray file;
    s >> function >> file >> frame.line;
    frame.function = QString::fromUtf8(function);
    frame.file = QString::fromUtf8(file);
146
    frame.usable = QFileInfo(frame.file).isReadable();
147 148 149
    return s;
}

hjk's avatar
hjk committed
150

151 152
class QmlEnginePrivate
{
153
public:
154 155
    explicit QmlEnginePrivate(QmlEngine *q);

156
private:
157
    friend class QmlEngine;
158
    int m_ping;
hjk's avatar
hjk committed
159 160
    QmlAdapter m_adapter;
    ApplicationLauncher m_applicationLauncher;
161 162
};

163
QmlEnginePrivate::QmlEnginePrivate(QmlEngine *q)
hjk's avatar
hjk committed
164
    : m_ping(0), m_adapter(q)
165 166
{}

167

168 169 170 171 172 173
///////////////////////////////////////////////////////////////////////
//
// QmlEngine
//
///////////////////////////////////////////////////////////////////////

174 175 176 177
QmlEngine::QmlEngine(const DebuggerStartParameters &startParameters,
        DebuggerEngine *masterEngine)
  : DebuggerEngine(startParameters, masterEngine),
    d(new QmlEnginePrivate(this))
178
{
Friedemann Kleint's avatar
Friedemann Kleint committed
179
    setObjectName(QLatin1String("QmlEngine"));
180 181 182
}

QmlEngine::~QmlEngine()
183
{}
184

185
void QmlEngine::gotoLocation(const Location &loc0)
186
{
187
    Location loc = loc0;
188
    if (isShadowBuildProject())
189 190
        loc.setFileName(fromShadowBuildFilename(loc0.fileName()));
    DebuggerEngine::gotoLocation(loc);
191 192
}

hjk's avatar
hjk committed
193
void QmlEngine::setupInferior()
194
{
hjk's avatar
hjk committed
195
    QTC_ASSERT(state() == InferiorSetupRequested, qDebug() << state());
Lasse Holmstedt's avatar
Lasse Holmstedt committed
196

197
    if (startParameters().startMode == AttachToRemote) {
Christian Kandeler's avatar
Christian Kandeler committed
198
        emit requestRemoteSetup();
199
    } else {
hjk's avatar
hjk committed
200 201 202 203 204 205 206 207 208
        connect(&d->m_applicationLauncher,
            SIGNAL(processExited(int)),
            SLOT(disconnected()));
        connect(&d->m_applicationLauncher,
            SIGNAL(appendMessage(QString,ProjectExplorer::OutputFormat)),
            SLOT(appendMessage(QString,ProjectExplorer::OutputFormat)));
        connect(&d->m_applicationLauncher,
            SIGNAL(bringToForegroundRequested(qint64)),
            runControl(),
209
            SLOT(bringApplicationToForeground(qint64)));
210 211 212 213 214 215

        d->m_applicationLauncher.setEnvironment(startParameters().environment);
        d->m_applicationLauncher.setWorkingDirectory(startParameters().workingDirectory);

        notifyInferiorSetupOk();
    }
hjk's avatar
hjk committed
216
}
hjk's avatar
hjk committed
217

hjk's avatar
hjk committed
218
void QmlEngine::appendMessage(const QString &msg, OutputFormat /* format */)
hjk's avatar
hjk committed
219
{
220
    showMessage(msg, AppOutput); // FIXME: Redirect to RunControl
hjk's avatar
hjk committed
221 222
}

Lasse Holmstedt's avatar
Lasse Holmstedt committed
223 224 225 226
void QmlEngine::connectionEstablished()
{
    attemptBreakpointSynchronization();

227 228
    ExtensionSystem::PluginManager *pluginManager =
        ExtensionSystem::PluginManager::instance();
hjk's avatar
hjk committed
229
    pluginManager->addObject(&d->m_adapter);
230
    pluginManager->addObject(this);
Lasse Holmstedt's avatar
Lasse Holmstedt committed
231

232
    showMessage(tr("QML Debugger connected."), StatusBar);
Lasse Holmstedt's avatar
Lasse Holmstedt committed
233 234

    notifyEngineRunAndInferiorRunOk();
235

Lasse Holmstedt's avatar
Lasse Holmstedt committed
236 237 238 239
}

void QmlEngine::connectionStartupFailed()
{
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
    QMessageBox::Button button =
            QMessageBox::critical(0, tr("Failed to connect to QML debugger"),
                                  tr("Qt Creator could not connect to the in-process debugger at %1:%2.\n"
                                     "Do you want to retry?")
                                  .arg(startParameters().qmlServerAddress)
                                  .arg(startParameters().qmlServerPort),
                                  QMessageBox::Retry | QMessageBox::Cancel | QMessageBox::Help,
                                  QMessageBox::Retry);

    switch (button) {
    case QMessageBox::Retry: {
        d->m_adapter.beginConnection();
        break;
    }
    case QMessageBox::Help: {
        Core::HelpManager *helpManager = Core::HelpManager::instance();
        helpManager->handleHelpRequest("qthelp://com.nokia.qtcreator/doc/creator-debugging-qml.html");
    }
    default:
        notifyEngineRunFailed();
Kai Koehne's avatar
Kai Koehne committed
260
        break;
261
    }
Lasse Holmstedt's avatar
Lasse Holmstedt committed
262 263
}

264
void QmlEngine::connectionError(QAbstractSocket::SocketError socketError)
Lasse Holmstedt's avatar
Lasse Holmstedt committed
265
{
hjk's avatar
hjk committed
266
    if (socketError == QAbstractSocket::RemoteHostClosedError)
267
        showMessage(tr("QML Debugger: Remote host closed connection."), StatusBar);
Lasse Holmstedt's avatar
Lasse Holmstedt committed
268 269
}

270 271
void QmlEngine::serviceConnectionError(const QString &serviceName)
{
272 273
    showMessage(tr("QML Debugger: Could not connect to service '%1'.")
        .arg(serviceName), StatusBar);
274 275
}

276 277 278 279 280
bool QmlEngine::canDisplayTooltip() const
{
    return state() == InferiorRunOk || state() == InferiorStopOk;
}

281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344
void QmlEngine::filterApplicationMessage(const QString &msg, int /*channel*/)
{
    static QString qddserver = QLatin1String("QDeclarativeDebugServer: ");
    static QString cannotRetrieve = "Cannot retrieve debugging output!";

    int index = msg.indexOf(qddserver);
    if (index != -1) {
        QString status = msg;
        status.remove(0, index + qddserver.length()); // chop of 'QDeclarativeDebugServer: '

        static QString waitingForConnection = QLatin1String("Waiting for connection on port");
        static QString unableToListen = QLatin1String("Unable to listen on port");
        static QString debuggingNotEnabled = QLatin1String("Ignoring \"-qmljsdebugger=port:");
        static QString connectionEstablished = QLatin1String("Connection established");

        QString errorMessage;
        if (status.startsWith(waitingForConnection)) {
            d->m_adapter.beginConnection();
        } else if (status.startsWith(unableToListen)) {
            errorMessage = tr("The port seems to be in use.");
        } else if (status.startsWith(debuggingNotEnabled)) {
            errorMessage = tr("The application isn't set up for QML/JS debugging.");
        } else if (status.startsWith(connectionEstablished)) {
            // nothing to do
        } else {
            qWarning() << "Unknown QDeclarativeDebugServer status message: " << status;
        }

        if (!errorMessage.isEmpty()) {
            notifyEngineRunFailed();

            Core::ICore * const core = Core::ICore::instance();
            QMessageBox *infoBox = new QMessageBox(core->mainWindow());
            infoBox->setIcon(QMessageBox::Critical);
            infoBox->setWindowTitle(tr("Qt Creator"));
            infoBox->setText(tr("Failed to connect to QML debugger\n\n"
                                "Qt Creator could not connect to the in-process debugger at %1:%2:\n"
                                "%3")
                             .arg(startParameters().qmlServerAddress)
                             .arg(startParameters().qmlServerPort)
                             .arg(errorMessage));
            infoBox->setStandardButtons(QMessageBox::Ok | QMessageBox::Help);
            infoBox->setDefaultButton(QMessageBox::Ok);
            infoBox->setModal(true);

            connect(infoBox, SIGNAL(finished(int)),
                    this, SLOT(messageBoxFinished(int)));

            infoBox->show();
        }
    } else if (msg.contains(cannotRetrieve)) {
        // we won't get debugging output, so just try to connect ...
        d->m_adapter.beginConnection();
    }
}

void QmlEngine::showMessage(const QString &msg, int channel, int timeout) const
{
    if (channel == AppOutput || channel == AppError) {
        const_cast<QmlEngine*>(this)->filterApplicationMessage(msg, channel);
    }
    DebuggerEngine::showMessage(msg, channel, timeout);
}

345 346
void QmlEngine::closeConnection()
{
347 348 349 350
    disconnect(&d->m_adapter, SIGNAL(connectionStartupFailed()),
        this, SLOT(connectionStartupFailed()));
    d->m_adapter.closeConnection();

hjk's avatar
hjk committed
351 352
    ExtensionSystem::PluginManager *pluginManager =
        ExtensionSystem::PluginManager::instance();
353

354
    if (pluginManager->allObjects().contains(this)) {
hjk's avatar
hjk committed
355
        pluginManager->removeObject(&d->m_adapter);
356 357 358 359
        pluginManager->removeObject(this);
    }
}

hjk's avatar
hjk committed
360 361 362
void QmlEngine::runEngine()
{
    QTC_ASSERT(state() == EngineRunRequested, qDebug() << state());
Lasse Holmstedt's avatar
Lasse Holmstedt committed
363

hjk's avatar
hjk committed
364
    if (!isSlaveEngine())
365 366 367 368 369 370
        startApplicationLauncher();
}

void QmlEngine::startApplicationLauncher()
{
    if (!d->m_applicationLauncher.isRunning()) {
371
        appendMessage(tr("Starting %1 %2").arg(QDir::toNativeSeparators(startParameters().executable), startParameters().processArgs), NormalMessageFormat);
hjk's avatar
hjk committed
372
        d->m_applicationLauncher.start(ApplicationLauncher::Gui,
373 374 375
                                    startParameters().executable,
                                    startParameters().processArgs);
    }
376
}
Lasse Holmstedt's avatar
Lasse Holmstedt committed
377

378 379 380 381 382 383
void QmlEngine::stopApplicationLauncher()
{
    if (d->m_applicationLauncher.isRunning()) {
        disconnect(&d->m_applicationLauncher, SIGNAL(processExited(int)), this, SLOT(disconnected()));
        d->m_applicationLauncher.stop();
    }
384 385
}

Christian Kandeler's avatar
Christian Kandeler committed
386
void QmlEngine::handleRemoteSetupDone(int gdbServerPort, int qmlPort)
387
{
Christian Kandeler's avatar
Christian Kandeler committed
388 389 390
    Q_UNUSED(gdbServerPort);
    if (qmlPort != -1)
        startParameters().qmlServerPort = qmlPort;
391 392 393 394 395 396 397 398 399 400
    notifyInferiorSetupOk();
}

void QmlEngine::handleRemoteSetupFailed(const QString &message)
{
    QMessageBox::critical(0,tr("Failed to start application"),
        tr("Application startup failed: %1").arg(message));
    notifyInferiorSetupFailed();
}

hjk's avatar
hjk committed
401
void QmlEngine::shutdownInferior()
402
{
403 404
    if (isSlaveEngine()) {
        resetLocation();
Lasse Holmstedt's avatar
Lasse Holmstedt committed
405
    }
406
    stopApplicationLauncher();
hjk's avatar
hjk committed
407 408 409 410 411
    notifyInferiorShutdownOk();
}

void QmlEngine::shutdownEngine()
{
412
    closeConnection();
Lasse Holmstedt's avatar
Lasse Holmstedt committed
413

414 415
    // double check (ill engine?):
    stopApplicationLauncher();
Lasse Holmstedt's avatar
Lasse Holmstedt committed
416

417
    notifyEngineShutdownOk();
418 419
    if (!isSlaveEngine())
        showMessage(QString(), StatusBar);
420 421
}

hjk's avatar
hjk committed
422
void QmlEngine::setupEngine()
423
{
424
    d->m_ping = 0;
hjk's avatar
hjk committed
425
    connect(&d->m_adapter, SIGNAL(connectionError(QAbstractSocket::SocketError)),
hjk's avatar
hjk committed
426
        SLOT(connectionError(QAbstractSocket::SocketError)));
hjk's avatar
hjk committed
427
    connect(&d->m_adapter, SIGNAL(serviceConnectionError(QString)),
hjk's avatar
hjk committed
428
        SLOT(serviceConnectionError(QString)));
hjk's avatar
hjk committed
429
    connect(&d->m_adapter, SIGNAL(connected()),
hjk's avatar
hjk committed
430
        SLOT(connectionEstablished()));
hjk's avatar
hjk committed
431
    connect(&d->m_adapter, SIGNAL(connectionStartupFailed()),
hjk's avatar
hjk committed
432
        SLOT(connectionStartupFailed()));
Lasse Holmstedt's avatar
Lasse Holmstedt committed
433

434
    notifyEngineSetupOk();
435 436
}

437 438
void QmlEngine::continueInferior()
{
hjk's avatar
hjk committed
439
    QTC_ASSERT(state() == InferiorStopOk, qDebug() << state());
440 441
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
442 443 444
    QByteArray cmd = "CONTINUE";
    rs << cmd;
    logMessage(LogSend, cmd);
445
    sendMessage(reply);
446
    resetLocation();
hjk's avatar
hjk committed
447 448
    notifyInferiorRunRequested();
    notifyInferiorRunOk();
449 450 451 452
}

void QmlEngine::interruptInferior()
{
453 454
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
455 456 457
    QByteArray cmd = "INTERRUPT";
    rs << cmd;
    logMessage(LogSend, cmd);
458
    sendMessage(reply);
459
    notifyInferiorStopOk();
460 461 462 463
}

void QmlEngine::executeStep()
{
464 465
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
466 467 468
    QByteArray cmd = "STEPINTO";
    rs << cmd;
    logMessage(LogSend, cmd);
469
    sendMessage(reply);
hjk's avatar
hjk committed
470 471
    notifyInferiorRunRequested();
    notifyInferiorRunOk();
472 473 474 475
}

void QmlEngine::executeStepI()
{
476 477
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
478 479 480
    QByteArray cmd = "STEPINTO";
    rs << cmd;
    logMessage(LogSend, cmd);
481
    sendMessage(reply);
hjk's avatar
hjk committed
482 483
    notifyInferiorRunRequested();
    notifyInferiorRunOk();
484 485 486 487
}

void QmlEngine::executeStepOut()
{
488 489
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
490 491 492
    QByteArray cmd = "STEPOUT";
    rs << cmd;
    logMessage(LogSend, cmd);
493
    sendMessage(reply);
hjk's avatar
hjk committed
494 495
    notifyInferiorRunRequested();
    notifyInferiorRunOk();
496 497 498 499
}

void QmlEngine::executeNext()
{
500 501
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
502 503 504
    QByteArray cmd = "STEPOVER";
    rs << cmd;
    logMessage(LogSend, cmd);
505
    sendMessage(reply);
hjk's avatar
hjk committed
506 507
    notifyInferiorRunRequested();
    notifyInferiorRunOk();
508 509 510 511
}

void QmlEngine::executeNextI()
{
512
    SDEBUG("QmlEngine::executeNextI()");
513 514
}

515
void QmlEngine::executeRunToLine(const ContextData &data)
516
{
517
    Q_UNUSED(data)
518 519 520 521 522 523 524 525 526
    SDEBUG("FIXME:  QmlEngine::executeRunToLine()");
}

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

527
void QmlEngine::executeJumpToLine(const ContextData &data)
528
{
529
    Q_UNUSED(data)
530 531 532 533 534
    XSDEBUG("FIXME:  QmlEngine::executeJumpToLine()");
}

void QmlEngine::activateFrame(int index)
{
535 536
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
537 538 539 540
    QByteArray cmd = "ACTIVATE_FRAME";
    rs << cmd
       << index;
    logMessage(LogSend, QString("%1 %2").arg(QString(cmd), QString::number(index)));
541
    sendMessage(reply);
542
    gotoLocation(stackHandler()->frames().value(index));
543 544 545 546 547 548 549 550 551
}

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

void QmlEngine::attemptBreakpointSynchronization()
{
hjk's avatar
hjk committed
552
    BreakHandler *handler = breakHandler();
553 554 555 556 557 558 559

    foreach (BreakpointId id, handler->unclaimedBreakpointIds()) {
        // Take ownership of the breakpoint. Requests insertion.
        if (acceptsBreakpoint(id))
            handler->setEngine(id, this);
    }

hjk's avatar
hjk committed
560 561
    JSAgentBreakpoints breakpoints;
    foreach (BreakpointId id, handler->engineBreakpointIds(this)) {
562 563 564 565 566 567 568 569
        if (handler->state(id) == BreakpointRemoveRequested) {
            handler->notifyBreakpointRemoveProceeding(id);
            handler->notifyBreakpointRemoveOk(id);
        } else {
            if (handler->state(id) == BreakpointInsertRequested) {
                handler->notifyBreakpointInsertProceeding(id);
            }
            QString processedFilename = handler->fileName(id);
570
#ifdef Q_OS_MACX
hjk's avatar
hjk committed
571 572
            // Qt Quick Applications by default copy the qml directory
            // to buildDir()/X.app/Contents/Resources
573 574 575
            const QString applicationBundleDir
                    = QFileInfo(startParameters().executable).absolutePath() + "/../..";
            processedFilename = mangleFilenamePaths(handler->fileName(id), startParameters().projectDir, applicationBundleDir + "/Contents/Resources");
576
#endif
577 578 579 580 581 582 583 584 585 586 587
            if (isShadowBuildProject())
                processedFilename = toShadowBuildFilename(processedFilename);
            JSAgentBreakpointData bp;
            bp.fileName = processedFilename.toUtf8();
            bp.lineNumber = handler->lineNumber(id);
            bp.functionName = handler->functionName(id).toUtf8();
            breakpoints.insert(bp);
            if (handler->state(id) == BreakpointInsertProceeding) {
                handler->notifyBreakpointInsertOk(id);
            }
        }
588 589 590 591
    }

    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
592 593 594 595 596 597 598 599 600 601 602
    QByteArray cmd = "BREAKPOINTS";
    rs << cmd
       << breakpoints;

    QStringList breakPointsStr;
    foreach (const JSAgentBreakpointData &bp, breakpoints) {
        breakPointsStr << QString("('%1' '%2' %3)").arg(QString(bp.functionName),
                                  QString(bp.fileName), QString::number(bp.lineNumber));
    }
    logMessage(LogSend, QString("%1 [%2]").arg(QString(cmd), breakPointsStr.join(", ")));

603
    sendMessage(reply);
604 605
}

606
bool QmlEngine::acceptsBreakpoint(BreakpointId id) const
607
{
608
    return !DebuggerEngine::isCppBreakpoint(breakHandler()->breakpointData(id));
609 610
}

611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634
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
//
//////////////////////////////////////////////////////////////////////

635
bool QmlEngine::setToolTipExpression(const QPoint &mousePos,
636
    TextEditor::ITextEditor *editor, const DebuggerToolTipContext &ctx)
637
{
hjk's avatar
hjk committed
638 639
    // This is processed by QML inspector, which has dependencies to 
    // the qml js editor. Makes life easier.
640
    emit tooltipRequested(mousePos, editor, ctx.position);
641
    return true;
642 643 644 645 646 647 648 649
}

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

hjk's avatar
hjk committed
650
void QmlEngine::assignValueInDebugger(const WatchData *,
hjk's avatar
hjk committed
651
    const QString &expression, const QVariant &valueV)
652
{
653 654 655 656 657 658 659 660
    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);
661 662
            QByteArray cmd = "SET_PROPERTY";
            rs << cmd;
663
            rs << expression.toUtf8() << objectId << property << valueV.toString();
664 665 666
            logMessage(LogSend, QString("%1 %2 %3 %4 %5").arg(
                                 QString(cmd), QString::number(objectId), QString(property),
                                 valueV.toString()));
667 668 669
            sendMessage(reply);
        }
    }
670 671
}

hjk's avatar
hjk committed
672 673
void QmlEngine::updateWatchData(const WatchData &data,
    const WatchUpdateFlags &)
674
{
Olivier Goffart's avatar
Olivier Goffart committed
675
//    qDebug() << "UPDATE WATCH DATA" << data.toString();
676
    //watchHandler()->rebuildModel();
677
    showStatusMessage(tr("Stopped."), 5000);
678

679
    if (!data.name.isEmpty() && data.isValueNeeded()) {
680 681
        QByteArray reply;
        QDataStream rs(&reply, QIODevice::WriteOnly);
682 683
        QByteArray cmd = "EXEC";
        rs << cmd;
684
        rs << data.iname << data.name;
685 686
        logMessage(LogSend, QString("%1 %2 %3").arg(QString(cmd), QString(data.iname),
                                                          QString(data.name)));
687 688 689
        sendMessage(reply);
    }

690 691
    if (!data.name.isEmpty() && data.isChildrenNeeded()
            && watchHandler()->isExpandedIName(data.iname))
Arvid Ephraim Picciani's avatar
Arvid Ephraim Picciani committed
692
        expandObject(data.iname, data.id);
693

694 695 696
    {
        QByteArray reply;
        QDataStream rs(&reply, QIODevice::WriteOnly);
697 698
        QByteArray cmd = "WATCH_EXPRESSIONS";
        rs << cmd;
699
        rs << watchHandler()->watchedExpressions();
700 701
        logMessage(LogSend, QString("%1 %2").arg(
                             QString(cmd), watchHandler()->watchedExpressions().join(", ")));
702 703
        sendMessage(reply);
    }
704 705 706

    if (!data.isSomethingNeeded())
        watchHandler()->insertData(data);
707 708
}

709
void QmlEngine::expandObject(const QByteArray &iname, quint64 objectId)
710 711 712
{
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
713 714
    QByteArray cmd = "EXPAND";
    rs << cmd;
715
    rs << iname << objectId;
716 717
    logMessage(LogSend, QString("%1 %2 %3").arg(QString(cmd), QString(iname),
                                                      QString::number(objectId)));
718 719 720
    sendMessage(reply);
}

721 722
void QmlEngine::sendPing()
{
723
    d->m_ping++;
724 725
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
726 727
    QByteArray cmd = "PING";
    rs << cmd;
728
    rs << d->m_ping;
729
    logMessage(LogSend, QString("%1 %2").arg(QString(cmd), QString::number(d->m_ping)));
730 731 732
    sendMessage(reply);
}

733 734 735 736 737 738 739 740 741 742 743 744
unsigned QmlEngine::debuggerCapabilities() const
{
    return AddWatcherCapability;
    /*ReverseSteppingCapability | SnapshotCapability
        | AutoDerefPointersCapability | DisassemblerCapability
        | RegisterCapability | ShowMemoryCapability
        | JumpToLineCapability | ReloadModuleCapability
        | ReloadModuleSymbolsCapability | BreakOnThrowAndCatchCapability
        | ReturnFromFunctionCapability
        | CreateFullBacktraceCapability
        | WatchpointCapability
        | AddWatcherCapability;*/
745 746 747 748 749 750 751 752 753 754
}

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

    QByteArray command;
    stream >> command;

755
    if (command == "STOPPED") {
756
        //qDebug() << command << this << state();
hjk's avatar
hjk committed
757
        if (state() == InferiorRunOk)
758
            notifyInferiorSpontaneousStop();
759

hjk's avatar
hjk committed
760
        QString logString = QString::fromLatin1(command);
761

hjk's avatar
hjk committed
762 763 764
        StackFrames stackFrames;
        QList<WatchData> watches;
        QList<WatchData> locals;
765
        stream >> stackFrames >> watches >> locals;
766

767 768
        logString += QString::fromLatin1(" (%1 stack frames) (%2 watches)  (%3 locals)").
                     arg(stackFrames.size()).arg(watches.size()).arg(locals.size());
769

770 771
        for (int i = 0; i != stackFrames.size(); ++i)
            stackFrames[i].level = i + 1;
772

773 774
        if (stackFrames.size() && stackFrames.back().function == "<global>")
            stackFrames.takeLast();
775 776 777
        stackHandler()->setFrames(stackFrames);

        watchHandler()->beginCycle();
778
        bool needPing = false;
779

hjk's avatar
hjk committed
780
        foreach (WatchData data, watches) {
781
            data.iname = watchHandler()->watcherName(data.exp);
782
            watchHandler()->insertData(data);
783

784 785
            if (watchHandler()->expandedINames().contains(data.iname)) {
                needPing = true;
Arvid Ephraim Picciani's avatar
Arvid Ephraim Picciani committed
786
                expandObject(data.iname, data.id);
787
            }
788 789
        }

hjk's avatar
hjk committed
790
        foreach (WatchData data, locals) {
791 792
            data.iname = "local." + data.exp;
            watchHandler()->insertData(data);
793

794 795
            if (watchHandler()->expandedINames().contains(data.iname)) {
                needPing = true;
Arvid Ephraim Picciani's avatar
Arvid Ephraim Picciani committed
796
                expandObject(data.iname, data.id);
797
            }
hjk's avatar
hjk committed
798 799
        }

800 801 802 803
        if (needPing)
            sendPing();
        else
            watchHandler()->endCycle();
804

805 806
        bool becauseOfException;
        stream >> becauseOfException;
807 808 809

        logString += becauseOfException ? " exception" : " no_exception";

810
        if (becauseOfException) {
811 812 813
            QString error;
            stream >> error;

con's avatar
con committed
814
            logString += QLatin1String(" ") + error;
815 816
            logMessage(LogReceive, logString);

817 818 819 820
            QString msg = stackFrames.isEmpty()
                ? tr("<p>An Uncaught Exception occured:</p><p>%2</p>")
                    .arg(Qt::escape(error))
                : tr("<p>An Uncaught Exception occured in <i>%1</i>:</p><p>%2</p>")
821
                    .arg(stackFrames.value(0).file, Qt::escape(error));
822
            showMessageBox(QMessageBox::Information, tr("Uncaught Exception"), msg);
823 824 825 826 827
        } else {
            //
            // Make breakpoint non-pending
            //
            QString file;
828
            QString function;
829 830 831 832 833
            int line = -1;

            if (!stackFrames.isEmpty()) {
                file = stackFrames.at(0).file;
                line = stackFrames.at(0).line;
834
                function = stackFrames.at(0).function;
835 836 837 838 839

                if (isShadowBuildProject()) {
                    file = fromShadowBuildFilename(file);
                }
            }
840

hjk's avatar
hjk committed
841 842 843 844
            BreakHandler *handler = breakHandler();
            foreach (BreakpointId id, handler->engineBreakpointIds(this)) {
                QString processedFilename = handler->fileName(id);
                if (processedFilename == file && handler->lineNumber(id) == line) {
845
                    QTC_ASSERT(handler->state(id) == BreakpointInserted,/**/);
hjk's avatar
hjk committed
846
                    BreakpointResponse br = handler->response(id);
847 848 849
                    br.fileName = file;
                    br.lineNumber = line;
                    br.functionName = function;
hjk's avatar
hjk committed
850
                    handler->setResponse(id, br);
851 852
                }
            }
853 854

            logMessage(LogReceive, logString);
855
        }
856 857 858 859

        if (!stackFrames.isEmpty())
            gotoLocation(stackFrames.value(0));

860
    } else if (command == "RESULT") {
hjk's avatar
hjk committed
861
        WatchData data;
862 863
        QByteArray iname;
        stream >> iname >> data;
864 865 866

        logMessage(LogReceive, QString("%1 %2 %3").arg(QString(command),
                                                             QString(iname), QString(data.value)));
867
        data.iname = iname;
868 869 870
        if (iname.startsWith("watch.")) {
            watchHandler()->insertData(data);
        } else if(iname == "console") {
871
            showMessage(data.value, ScriptConsoleOutput);
872 873 874
        } else {
            qWarning() << "QmlEngine: Unexcpected result: " << iname << data.value;
        }
875
    } else if (command == "EXPANDED") {
hjk's avatar
hjk committed
876
        QList<WatchData> result;
877 878
        QByteArray iname;
        stream >> iname >> result;
879 880 881

        logMessage(LogReceive, QString("%1 %2 (%3 x watchdata)").arg(
                             QString(command), QString(iname), QString::number(result.size())));
882
        bool needPing = false;
hjk's avatar
hjk committed
883
        foreach (WatchData data, result) {
884 885 886
            data.iname = iname + '.' + data.exp;
            watchHandler()->insertData(data);

887 888
            if (watchHandler()->expandedINames().contains(data.iname)) {
                needPing = true;
Arvid Ephraim Picciani's avatar
Arvid Ephraim Picciani committed
889
                expandObject(data.iname, data.id);
890
            }
891
        }
892 893
        if (needPing)
            sendPing();
894
    } else if (command == "LOCALS") {
hjk's avatar
hjk committed
895
        QList<WatchData> locals;
896 897
        int frameId;
        stream >> frameId >> locals;
898 899 900 901

        logMessage(LogReceive, QString("%1 %2 (%3 x locals)").arg(
                             QString(command), QString::number(frameId),
                             QString::number(locals.size())));
902
        watchHandler()->beginCycle();
903
        bool needPing = false;
hjk's avatar
hjk committed
904
        foreach (WatchData data, locals) {
905 906
            data.iname = "local." + data.exp;
            watchHandler()->insertData(data);
907 908
            if (watchHandler()->expandedINames().contains(data.iname)) {
                needPing = true;
Arvid Ephraim Picciani's avatar
Arvid Ephraim Picciani committed
909
                expandObject(data.iname, data.id);
910
            }
911
        }
912 913 914 915 916 917 918 919
        if (needPing)
            sendPing();
        else
            watchHandler()->endCycle();

    } else if (command == "PONG") {
        int ping;
        stream >> ping;
920 921 922

        logMessage(LogReceive, QString("%1 %2").arg(QString(command), QString::number(ping)));

923
        if (ping == d->m_ping)
924
            watchHandler()->endCycle();
925 926
    } else {
        qDebug() << Q_FUNC_INFO << "Unknown command: " << command;
927
        logMessage(LogReceive, QString("%1 UNKNOWN COMMAND!!").arg(QString(command)));
928 929 930
    }
}

931 932
void QmlEngine::disconnected()
{