qmlengine.cpp 32.9 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
#include <utils/abstractprocess.h>
59
#include <utils/qtcassert.h>
60
#include <utils/fileinprojectfinder.h>
61

62
#include <coreplugin/icore.h>
63 64
#include <coreplugin/helpmanager.h>

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

#include <QtNetwork/QTcpSocket>
79 80
#include <QtNetwork/QHostAddress>

81 82 83 84 85 86 87 88
#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
89 90
using namespace ProjectExplorer;

91 92 93
namespace Debugger {
namespace Internal {

94 95 96
struct JSAgentBreakpointData
{
    QByteArray functionName;
97 98 99 100 101 102 103 104
    QByteArray fileUrl;
    qint32 lineNumber;
};

struct JSAgentStackData
{
    QByteArray functionName;
    QByteArray fileUrl;
105 106 107 108 109
    qint32 lineNumber;
};

uint qHash(const JSAgentBreakpointData &b)
{
110
    return b.lineNumber ^ qHash(b.fileUrl);
111 112 113 114
}

QDataStream &operator<<(QDataStream &s, const JSAgentBreakpointData &data)
{
115 116 117 118 119 120
    return s << data.functionName << data.fileUrl << data.lineNumber;
}

QDataStream &operator<<(QDataStream &s, const JSAgentStackData &data)
{
    return s << data.functionName << data.fileUrl << data.lineNumber;
121 122 123 124
}

QDataStream &operator>>(QDataStream &s, JSAgentBreakpointData &data)
{
125 126 127 128 129 130
    return s >> data.functionName >> data.fileUrl >> data.lineNumber;
}

QDataStream &operator>>(QDataStream &s, JSAgentStackData &data)
{
    return s >> data.functionName >> data.fileUrl >> data.lineNumber;
131 132 133 134
}

bool operator==(const JSAgentBreakpointData &b1, const JSAgentBreakpointData &b2)
{
135
    return b1.lineNumber == b2.lineNumber && b1.fileUrl == b2.fileUrl;
136 137 138
}

typedef QSet<JSAgentBreakpointData> JSAgentBreakpoints;
139
typedef QList<JSAgentStackData> JSAgentStackFrames;
140 141


142
static QDataStream &operator>>(QDataStream &s, WatchData &data)
143 144
{
    data = WatchData();
145 146 147 148 149 150 151 152
    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));
153
    data.setHasChildren(hasChildren);
154
    data.setAllUnneeded();
155 156 157
    return s;
}

158 159
class QmlEnginePrivate
{
160
public:
161 162
    explicit QmlEnginePrivate(QmlEngine *q);

163
private:
164
    friend class QmlEngine;
165
    int m_ping;
hjk's avatar
hjk committed
166 167
    QmlAdapter m_adapter;
    ApplicationLauncher m_applicationLauncher;
168
    Utils::FileInProjectFinder fileFinder;
169 170
};

171
QmlEnginePrivate::QmlEnginePrivate(QmlEngine *q)
hjk's avatar
hjk committed
172
    : m_ping(0), m_adapter(q)
173 174
{}

175

176 177 178 179 180 181
///////////////////////////////////////////////////////////////////////
//
// QmlEngine
//
///////////////////////////////////////////////////////////////////////

182 183 184 185
QmlEngine::QmlEngine(const DebuggerStartParameters &startParameters,
        DebuggerEngine *masterEngine)
  : DebuggerEngine(startParameters, masterEngine),
    d(new QmlEnginePrivate(this))
186
{
Friedemann Kleint's avatar
Friedemann Kleint committed
187
    setObjectName(QLatin1String("QmlEngine"));
188 189 190
}

QmlEngine::~QmlEngine()
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

Christiaan Janssen's avatar
Christiaan Janssen committed
234 235
    synchronizeWatchers();

Lasse Holmstedt's avatar
Lasse Holmstedt committed
236
    notifyEngineRunAndInferiorRunOk();
237

Lasse Holmstedt's avatar
Lasse Holmstedt committed
238 239 240 241
}

void QmlEngine::connectionStartupFailed()
{
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
    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("Could not connect to the in-process QML debugger.\n"
                        "Do you want to retry?"));
    infoBox->setStandardButtons(QMessageBox::Retry | QMessageBox::Cancel | QMessageBox::Help);
    infoBox->setDefaultButton(QMessageBox::Retry);
    infoBox->setModal(true);

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

    infoBox->show();
}

void QmlEngine::retryMessageBoxFinished(int result)
{
    switch (result) {
261 262 263 264 265 266 267
    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");
268
        // fall through
269 270 271
    }
    default:
        notifyEngineRunFailed();
Kai Koehne's avatar
Kai Koehne committed
272
        break;
273
    }
Lasse Holmstedt's avatar
Lasse Holmstedt committed
274 275
}

276
void QmlEngine::connectionError(QAbstractSocket::SocketError socketError)
Lasse Holmstedt's avatar
Lasse Holmstedt committed
277
{
hjk's avatar
hjk committed
278
    if (socketError == QAbstractSocket::RemoteHostClosedError)
279
        showMessage(tr("QML Debugger: Remote host closed connection."), StatusBar);
Lasse Holmstedt's avatar
Lasse Holmstedt committed
280 281
}

282 283
void QmlEngine::serviceConnectionError(const QString &serviceName)
{
284 285
    showMessage(tr("QML Debugger: Could not connect to service '%1'.")
        .arg(serviceName), StatusBar);
286 287
}

288 289 290 291 292
bool QmlEngine::canDisplayTooltip() const
{
    return state() == InferiorRunOk || state() == InferiorStopOk;
}

293 294
void QmlEngine::filterApplicationMessage(const QString &msg, int /*channel*/)
{
Friedemann Kleint's avatar
Friedemann Kleint committed
295 296
    static const QString qddserver = QLatin1String("QDeclarativeDebugServer: ");
    static const QString cannotRetrieveDebuggingOutput = Utils::AbstractProcess::msgWinCannotRetrieveDebuggingOutput();
297

298
    const int index = msg.indexOf(qddserver);
299 300 301 302 303 304 305 306 307 308 309 310 311
    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)) {
312
            //: Error message shown after 'Could not connect ... debugger:"
313 314
            errorMessage = tr("The port seems to be in use.");
        } else if (status.startsWith(debuggingNotEnabled)) {
315 316
            //: Error message shown after 'Could not connect ... debugger:"
            errorMessage = tr("The application is not set up for QML/JS debugging.");
317 318 319 320 321 322 323 324 325 326 327 328 329
        } 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"));
Friedemann Kleint's avatar
Friedemann Kleint committed
330 331
            //: %1 is detailed error message
            infoBox->setText(tr("Could not connect to the in-process QML debugger:\n%1")
332 333 334 335 336 337
                             .arg(errorMessage));
            infoBox->setStandardButtons(QMessageBox::Ok | QMessageBox::Help);
            infoBox->setDefaultButton(QMessageBox::Ok);
            infoBox->setModal(true);

            connect(infoBox, SIGNAL(finished(int)),
338
                    this, SLOT(wrongSetupMessageBoxFinished(int)));
339 340 341

            infoBox->show();
        }
Friedemann Kleint's avatar
Friedemann Kleint committed
342
    } else if (msg.contains(cannotRetrieveDebuggingOutput)) {
343 344 345 346 347 348 349 350 351 352 353 354 355
        // 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);
}

356 357
void QmlEngine::closeConnection()
{
358 359 360 361
    disconnect(&d->m_adapter, SIGNAL(connectionStartupFailed()),
        this, SLOT(connectionStartupFailed()));
    d->m_adapter.closeConnection();

hjk's avatar
hjk committed
362 363
    ExtensionSystem::PluginManager *pluginManager =
        ExtensionSystem::PluginManager::instance();
364

365
    if (pluginManager->allObjects().contains(this)) {
hjk's avatar
hjk committed
366
        pluginManager->removeObject(&d->m_adapter);
367 368 369 370
        pluginManager->removeObject(this);
    }
}

hjk's avatar
hjk committed
371 372 373
void QmlEngine::runEngine()
{
    QTC_ASSERT(state() == EngineRunRequested, qDebug() << state());
Lasse Holmstedt's avatar
Lasse Holmstedt committed
374

hjk's avatar
hjk committed
375
    if (!isSlaveEngine())
376 377 378 379 380 381
        startApplicationLauncher();
}

void QmlEngine::startApplicationLauncher()
{
    if (!d->m_applicationLauncher.isRunning()) {
382
        appendMessage(tr("Starting %1 %2").arg(QDir::toNativeSeparators(startParameters().executable), startParameters().processArgs), NormalMessageFormat);
hjk's avatar
hjk committed
383
        d->m_applicationLauncher.start(ApplicationLauncher::Gui,
384 385 386
                                    startParameters().executable,
                                    startParameters().processArgs);
    }
387
}
Lasse Holmstedt's avatar
Lasse Holmstedt committed
388

389 390 391 392 393 394
void QmlEngine::stopApplicationLauncher()
{
    if (d->m_applicationLauncher.isRunning()) {
        disconnect(&d->m_applicationLauncher, SIGNAL(processExited(int)), this, SLOT(disconnected()));
        d->m_applicationLauncher.stop();
    }
395 396
}

Christian Kandeler's avatar
Christian Kandeler committed
397
void QmlEngine::handleRemoteSetupDone(int gdbServerPort, int qmlPort)
398
{
Christian Kandeler's avatar
Christian Kandeler committed
399 400 401
    Q_UNUSED(gdbServerPort);
    if (qmlPort != -1)
        startParameters().qmlServerPort = qmlPort;
402 403 404 405 406 407 408 409 410 411
    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
412
void QmlEngine::shutdownInferior()
413
{
414 415
    if (isSlaveEngine()) {
        resetLocation();
Lasse Holmstedt's avatar
Lasse Holmstedt committed
416
    }
417
    stopApplicationLauncher();
hjk's avatar
hjk committed
418 419 420 421 422
    notifyInferiorShutdownOk();
}

void QmlEngine::shutdownEngine()
{
423
    closeConnection();
Lasse Holmstedt's avatar
Lasse Holmstedt committed
424

425 426
    // double check (ill engine?):
    stopApplicationLauncher();
Lasse Holmstedt's avatar
Lasse Holmstedt committed
427

428
    notifyEngineShutdownOk();
429 430
    if (!isSlaveEngine())
        showMessage(QString(), StatusBar);
431 432
}

hjk's avatar
hjk committed
433
void QmlEngine::setupEngine()
434
{
435
    d->m_ping = 0;
hjk's avatar
hjk committed
436
    connect(&d->m_adapter, SIGNAL(connectionError(QAbstractSocket::SocketError)),
hjk's avatar
hjk committed
437
        SLOT(connectionError(QAbstractSocket::SocketError)));
hjk's avatar
hjk committed
438
    connect(&d->m_adapter, SIGNAL(serviceConnectionError(QString)),
hjk's avatar
hjk committed
439
        SLOT(serviceConnectionError(QString)));
hjk's avatar
hjk committed
440
    connect(&d->m_adapter, SIGNAL(connected()),
hjk's avatar
hjk committed
441
        SLOT(connectionEstablished()));
hjk's avatar
hjk committed
442
    connect(&d->m_adapter, SIGNAL(connectionStartupFailed()),
hjk's avatar
hjk committed
443
        SLOT(connectionStartupFailed()));
Lasse Holmstedt's avatar
Lasse Holmstedt committed
444

445
    notifyEngineSetupOk();
446 447
}

448 449
void QmlEngine::continueInferior()
{
hjk's avatar
hjk committed
450
    QTC_ASSERT(state() == InferiorStopOk, qDebug() << state());
451 452
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
453 454 455
    QByteArray cmd = "CONTINUE";
    rs << cmd;
    logMessage(LogSend, cmd);
456
    sendMessage(reply);
457
    resetLocation();
hjk's avatar
hjk committed
458 459
    notifyInferiorRunRequested();
    notifyInferiorRunOk();
460 461 462 463
}

void QmlEngine::interruptInferior()
{
464 465
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
466 467 468
    QByteArray cmd = "INTERRUPT";
    rs << cmd;
    logMessage(LogSend, cmd);
469
    sendMessage(reply);
470
    notifyInferiorStopOk();
471 472 473 474
}

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

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

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

void QmlEngine::executeNext()
{
511 512
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
513 514 515
    QByteArray cmd = "STEPOVER";
    rs << cmd;
    logMessage(LogSend, cmd);
516
    sendMessage(reply);
hjk's avatar
hjk committed
517 518
    notifyInferiorRunRequested();
    notifyInferiorRunOk();
519 520 521 522
}

void QmlEngine::executeNextI()
{
523
    SDEBUG("QmlEngine::executeNextI()");
524 525
}

526
void QmlEngine::executeRunToLine(const ContextData &data)
527
{
528
    Q_UNUSED(data)
529 530 531 532 533 534 535 536 537
    SDEBUG("FIXME:  QmlEngine::executeRunToLine()");
}

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

538
void QmlEngine::executeJumpToLine(const ContextData &data)
539
{
540
    Q_UNUSED(data)
541 542 543 544 545
    XSDEBUG("FIXME:  QmlEngine::executeJumpToLine()");
}

void QmlEngine::activateFrame(int index)
{
546 547
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
548 549 550 551
    QByteArray cmd = "ACTIVATE_FRAME";
    rs << cmd
       << index;
    logMessage(LogSend, QString("%1 %2").arg(QString(cmd), QString::number(index)));
552
    sendMessage(reply);
553
    gotoLocation(stackHandler()->frames().value(index));
554 555 556 557 558 559 560 561 562
}

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

void QmlEngine::attemptBreakpointSynchronization()
{
hjk's avatar
hjk committed
563
    BreakHandler *handler = breakHandler();
564 565 566 567 568 569 570

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

hjk's avatar
hjk committed
571 572
    JSAgentBreakpoints breakpoints;
    foreach (BreakpointId id, handler->engineBreakpointIds(this)) {
573 574 575 576 577 578 579 580
        if (handler->state(id) == BreakpointRemoveRequested) {
            handler->notifyBreakpointRemoveProceeding(id);
            handler->notifyBreakpointRemoveOk(id);
        } else {
            if (handler->state(id) == BreakpointInsertRequested) {
                handler->notifyBreakpointInsertProceeding(id);
            }
            JSAgentBreakpointData bp;
581
            bp.fileUrl = QUrl::fromLocalFile(handler->fileName(id)).toString().toUtf8();
582 583 584 585 586 587 588
            bp.lineNumber = handler->lineNumber(id);
            bp.functionName = handler->functionName(id).toUtf8();
            breakpoints.insert(bp);
            if (handler->state(id) == BreakpointInsertProceeding) {
                handler->notifyBreakpointInsertOk(id);
            }
        }
589 590 591 592
    }

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

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

604
    sendMessage(reply);
605 606
}

607
bool QmlEngine::acceptsBreakpoint(BreakpointId id) const
608
{
609
    return !DebuggerEngine::isCppBreakpoint(breakHandler()->breakpointData(id));
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 635
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
//
//////////////////////////////////////////////////////////////////////

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

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

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

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

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

691
    if (!data.name.isEmpty() && data.isChildrenNeeded()
Christiaan Janssen's avatar
Christiaan Janssen committed
692
            && watchHandler()->isExpandedIName(data.iname)) {
Arvid Ephraim Picciani's avatar
Arvid Ephraim Picciani committed
693
        expandObject(data.iname, data.id);
Christiaan Janssen's avatar
Christiaan Janssen committed
694 695 696
    }

    synchronizeWatchers();
697

Christiaan Janssen's avatar
Christiaan Janssen committed
698 699 700 701 702 703 704 705
    if (!data.isSomethingNeeded())
        watchHandler()->insertData(data);
}

void QmlEngine::synchronizeWatchers()
{
    if (!watchHandler()->watcherNames().isEmpty()) {
        // send watchers list
706 707
        QByteArray reply;
        QDataStream rs(&reply, QIODevice::WriteOnly);
708 709
        QByteArray cmd = "WATCH_EXPRESSIONS";
        rs << cmd;
710
        rs << watchHandler()->watchedExpressions();
711
        logMessage(LogSend, QString("%1 %2").arg(
Christiaan Janssen's avatar
Christiaan Janssen committed
712
                       QString(cmd), watchHandler()->watchedExpressions().join(", ")));
713 714
        sendMessage(reply);
    }
715 716
}

717
void QmlEngine::expandObject(const QByteArray &iname, quint64 objectId)
718 719 720
{
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
721 722
    QByteArray cmd = "EXPAND";
    rs << cmd;
723
    rs << iname << objectId;
724 725
    logMessage(LogSend, QString("%1 %2 %3").arg(QString(cmd), QString(iname),
                                                      QString::number(objectId)));
726 727 728
    sendMessage(reply);
}

729 730
void QmlEngine::sendPing()
{
731
    d->m_ping++;
732 733
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
734 735
    QByteArray cmd = "PING";
    rs << cmd;
736
    rs << d->m_ping;
737
    logMessage(LogSend, QString("%1 %2").arg(QString(cmd), QString::number(d->m_ping)));
738 739 740
    sendMessage(reply);
}

741 742 743 744 745 746 747 748 749 750 751 752
unsigned QmlEngine::debuggerCapabilities() const
{
    return AddWatcherCapability;
    /*ReverseSteppingCapability | SnapshotCapability
        | AutoDerefPointersCapability | DisassemblerCapability
        | RegisterCapability | ShowMemoryCapability
        | JumpToLineCapability | ReloadModuleCapability
        | ReloadModuleSymbolsCapability | BreakOnThrowAndCatchCapability
        | ReturnFromFunctionCapability
        | CreateFullBacktraceCapability
        | WatchpointCapability
        | AddWatcherCapability;*/
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
QString QmlEngine::toFileInProject(const QString &fileUrl)
{
    if (fileUrl.isEmpty())
        return fileUrl;

    const QString path = QUrl(fileUrl).path();

    // Try to find shadow-build file in source dir first
    if (!QUrl(fileUrl).toLocalFile().isEmpty()
            && isShadowBuildProject()) {
        const QString sourcePath = fromShadowBuildFilename(path);
        if (QFileInfo(sourcePath).exists())
            return sourcePath;
    }

    // Try whether file is absolute & exists
    if (QFileInfo(path).isAbsolute()
            && QFileInfo(path).exists()) {
        return path;
    }

    if (d->fileFinder.projectDirectory().isEmpty())
        d->fileFinder.setProjectDirectory(startParameters().projectDir);

    // Try to find file with biggest common path in source directory
    bool fileFound = false;
    QString fileInProject = d->fileFinder.findFile(path, &fileFound);
    if (fileFound)
        return fileInProject;
    return fileUrl;
}

787 788 789 790 791 792 793 794
void QmlEngine::messageReceived(const QByteArray &message)
{
    QByteArray rwData = message;
    QDataStream stream(&rwData, QIODevice::ReadOnly);

    QByteArray command;
    stream >> command;

795
    if (command == "STOPPED") {
796
        //qDebug() << command << this << state();
hjk's avatar
hjk committed
797
        if (state() == InferiorRunOk)
798
            notifyInferiorSpontaneousStop();
799

hjk's avatar
hjk committed
800
        QString logString = QString::fromLatin1(command);
801

802
        JSAgentStackFrames stackFrames;
hjk's avatar
hjk committed
803 804
        QList<WatchData> watches;
        QList<WatchData> locals;
805
        stream >> stackFrames >> watches >> locals;
806

807 808
        logString += QString::fromLatin1(" (%1 stack frames) (%2 watches)  (%3 locals)").
                     arg(stackFrames.size()).arg(watches.size()).arg(locals.size());
809

810 811 812 813 814 815 816 817 818 819
        StackFrames ideStackFrames;
        for (int i = 0; i != stackFrames.size(); ++i) {
            StackFrame frame;
            frame.line = stackFrames.at(i).lineNumber;
            frame.function = stackFrames.at(i).functionName;
            frame.file = toFileInProject(stackFrames.at(i).fileUrl);
            frame.usable = QFileInfo(frame.file).isReadable();
            frame.level = i + 1;
            ideStackFrames << frame;
        }
820

821 822 823
        if (ideStackFrames.size() && ideStackFrames.back().function == "<global>")
            ideStackFrames.takeLast();
        stackHandler()->setFrames(ideStackFrames);
824 825

        watchHandler()->beginCycle();
826
        bool needPing = false;
827

hjk's avatar
hjk committed
828
        foreach (WatchData data, watches) {
829
            data.iname = watchHandler()->watcherName(data.exp);
830
            watchHandler()->insertData(data);
831

832 833
            if (watchHandler()->expandedINames().contains(data.iname)) {
                needPing = true;
Arvid Ephraim Picciani's avatar
Arvid Ephraim Picciani committed
834
                expandObject(data.iname, data.id);
835
            }
836 837
        }

hjk's avatar
hjk committed
838
        foreach (WatchData data, locals) {
839 840
            data.iname = "local." + data.exp;
            watchHandler()->insertData(data);
841

842 843
            if (watchHandler()->expandedINames().contains(data.iname)) {
                needPing = true;
Arvid Ephraim Picciani's avatar
Arvid Ephraim Picciani committed
844
                expandObject(data.iname, data.id);
845
            }
hjk's avatar
hjk committed
846 847
        }

848
        if (needPing) {
849
            sendPing();
850
        } else {
851
            watchHandler()->endCycle();
852
        }
853

854 855
        bool becauseOfException;
        stream >> becauseOfException;
856 857 858

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

859
        if (becauseOfException) {
860 861 862
            QString error;
            stream >> error;

Friedemann Kleint's avatar
Friedemann Kleint committed
863 864
            logString += QLatin1Char(' ');
            logString += error;
865 866
            logMessage(LogReceive, logString);

867
            QString msg = stackFrames.isEmpty()
Friedemann Kleint's avatar
Friedemann Kleint committed
868
                ? tr("<p>An uncaught exception occurred:</p><p>%1</p>")
869
                    .arg(Qt::escape(error))
Friedemann Kleint's avatar
Friedemann Kleint committed
870
                : tr("<p>An uncaught exception occurred in <i>%1</i>:</p><p>%2</p>")
871
                    .arg(stackFrames.value(0).fileUrl, Qt::escape(error));
872
            showMessageBox(QMessageBox::Information, tr("Uncaught Exception"), msg);
873 874 875 876 877
        } else {
            //
            // Make breakpoint non-pending
            //
            QString file;
878
            QString function;
879 880
            int line = -1;

881 882 883 884
            if (!ideStackFrames.isEmpty()) {
                file = ideStackFrames.at(0).file;
                line = ideStackFrames.at(0).line;
                function = ideStackFrames.at(0).function;
885
            }
886

hjk's avatar
hjk committed
887 888 889 890
            BreakHandler *handler = breakHandler();
            foreach (BreakpointId id, handler->engineBreakpointIds(this)) {
                QString processedFilename = handler->fileName(id);
                if (processedFilename == file && handler->lineNumber(id) == line) {
891
                    QTC_ASSERT(handler->state(id) == BreakpointInserted,/**/);
hjk's avatar
hjk committed
892
                    BreakpointResponse br = handler->response(id);