qmlengine.cpp 33 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 358 359 360
bool QmlEngine::acceptsWatchesWhileRunning() const
{
    return true;
}

361 362
void QmlEngine::closeConnection()
{
363 364 365 366
    disconnect(&d->m_adapter, SIGNAL(connectionStartupFailed()),
        this, SLOT(connectionStartupFailed()));
    d->m_adapter.closeConnection();

hjk's avatar
hjk committed
367 368
    ExtensionSystem::PluginManager *pluginManager =
        ExtensionSystem::PluginManager::instance();
369

370
    if (pluginManager->allObjects().contains(this)) {
hjk's avatar
hjk committed
371
        pluginManager->removeObject(&d->m_adapter);
372 373 374 375
        pluginManager->removeObject(this);
    }
}

hjk's avatar
hjk committed
376 377 378
void QmlEngine::runEngine()
{
    QTC_ASSERT(state() == EngineRunRequested, qDebug() << state());
Lasse Holmstedt's avatar
Lasse Holmstedt committed
379

hjk's avatar
hjk committed
380
    if (!isSlaveEngine())
381 382 383 384 385 386
        startApplicationLauncher();
}

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

394 395 396 397 398 399
void QmlEngine::stopApplicationLauncher()
{
    if (d->m_applicationLauncher.isRunning()) {
        disconnect(&d->m_applicationLauncher, SIGNAL(processExited(int)), this, SLOT(disconnected()));
        d->m_applicationLauncher.stop();
    }
400 401
}

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

void QmlEngine::shutdownEngine()
{
428
    closeConnection();
Lasse Holmstedt's avatar
Lasse Holmstedt committed
429

430 431
    // double check (ill engine?):
    stopApplicationLauncher();
Lasse Holmstedt's avatar
Lasse Holmstedt committed
432

433
    notifyEngineShutdownOk();
434 435
    if (!isSlaveEngine())
        showMessage(QString(), StatusBar);
436 437
}

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

450
    notifyEngineSetupOk();
451 452
}

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

void QmlEngine::interruptInferior()
{
469 470
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
471 472 473
    QByteArray cmd = "INTERRUPT";
    rs << cmd;
    logMessage(LogSend, cmd);
474
    sendMessage(reply);
475
    notifyInferiorStopOk();
476 477 478 479
}

void QmlEngine::executeStep()
{
480 481
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
482 483 484
    QByteArray cmd = "STEPINTO";
    rs << cmd;
    logMessage(LogSend, cmd);
485
    sendMessage(reply);
hjk's avatar
hjk committed
486 487
    notifyInferiorRunRequested();
    notifyInferiorRunOk();
488 489 490 491
}

void QmlEngine::executeStepI()
{
492 493
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
494 495 496
    QByteArray cmd = "STEPINTO";
    rs << cmd;
    logMessage(LogSend, cmd);
497
    sendMessage(reply);
hjk's avatar
hjk committed
498 499
    notifyInferiorRunRequested();
    notifyInferiorRunOk();
500 501 502 503
}

void QmlEngine::executeStepOut()
{
504 505
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
506 507 508
    QByteArray cmd = "STEPOUT";
    rs << cmd;
    logMessage(LogSend, cmd);
509
    sendMessage(reply);
hjk's avatar
hjk committed
510 511
    notifyInferiorRunRequested();
    notifyInferiorRunOk();
512 513 514 515
}

void QmlEngine::executeNext()
{
516 517
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
518 519 520
    QByteArray cmd = "STEPOVER";
    rs << cmd;
    logMessage(LogSend, cmd);
521
    sendMessage(reply);
hjk's avatar
hjk committed
522 523
    notifyInferiorRunRequested();
    notifyInferiorRunOk();
524 525 526 527
}

void QmlEngine::executeNextI()
{
528
    SDEBUG("QmlEngine::executeNextI()");
529 530
}

531
void QmlEngine::executeRunToLine(const ContextData &data)
532
{
533
    Q_UNUSED(data)
534 535 536 537 538 539 540 541 542
    SDEBUG("FIXME:  QmlEngine::executeRunToLine()");
}

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

543
void QmlEngine::executeJumpToLine(const ContextData &data)
544
{
545
    Q_UNUSED(data)
546 547 548 549 550
    XSDEBUG("FIXME:  QmlEngine::executeJumpToLine()");
}

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

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

void QmlEngine::attemptBreakpointSynchronization()
{
hjk's avatar
hjk committed
568
    BreakHandler *handler = breakHandler();
569 570 571 572 573 574 575

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

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

    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
598 599 600 601 602 603 604
    QByteArray cmd = "BREAKPOINTS";
    rs << cmd
       << breakpoints;

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

609
    sendMessage(reply);
610 611
}

612
bool QmlEngine::acceptsBreakpoint(BreakpointId id) const
613
{
614
    return !DebuggerEngine::isCppBreakpoint(breakHandler()->breakpointData(id));
615 616
}

617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640
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
//
//////////////////////////////////////////////////////////////////////

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

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

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

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

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

696
    if (!data.name.isEmpty() && data.isChildrenNeeded()
Christiaan Janssen's avatar
Christiaan Janssen committed
697
            && watchHandler()->isExpandedIName(data.iname)) {
Arvid Ephraim Picciani's avatar
Arvid Ephraim Picciani committed
698
        expandObject(data.iname, data.id);
Christiaan Janssen's avatar
Christiaan Janssen committed
699 700 701
    }

    synchronizeWatchers();
702

Christiaan Janssen's avatar
Christiaan Janssen committed
703 704 705 706 707 708 709 710
    if (!data.isSomethingNeeded())
        watchHandler()->insertData(data);
}

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

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

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

746 747 748 749 750 751 752 753 754 755 756 757
unsigned QmlEngine::debuggerCapabilities() const
{
    return AddWatcherCapability;
    /*ReverseSteppingCapability | SnapshotCapability
        | AutoDerefPointersCapability | DisassemblerCapability
        | RegisterCapability | ShowMemoryCapability
        | JumpToLineCapability | ReloadModuleCapability
        | ReloadModuleSymbolsCapability | BreakOnThrowAndCatchCapability
        | ReturnFromFunctionCapability
        | CreateFullBacktraceCapability
        | WatchpointCapability
        | AddWatcherCapability;*/
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
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;
}

792 793 794 795 796 797 798 799
void QmlEngine::messageReceived(const QByteArray &message)
{
    QByteArray rwData = message;
    QDataStream stream(&rwData, QIODevice::ReadOnly);

    QByteArray command;
    stream >> command;

800
    if (command == "STOPPED") {
801
        //qDebug() << command << this << state();
hjk's avatar
hjk committed
802
        if (state() == InferiorRunOk)
803
            notifyInferiorSpontaneousStop();
804

hjk's avatar
hjk committed
805
        QString logString = QString::fromLatin1(command);
806

807
        JSAgentStackFrames stackFrames;
hjk's avatar
hjk committed
808 809
        QList<WatchData> watches;
        QList<WatchData> locals;
810
        stream >> stackFrames >> watches >> locals;
811

812 813
        logString += QString::fromLatin1(" (%1 stack frames) (%2 watches)  (%3 locals)").
                     arg(stackFrames.size()).arg(watches.size()).arg(locals.size());
814

815 816 817 818 819 820 821 822 823 824
        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;
        }
825

826 827 828
        if (ideStackFrames.size() && ideStackFrames.back().function == "<global>")
            ideStackFrames.takeLast();
        stackHandler()->setFrames(ideStackFrames);
829 830

        watchHandler()->beginCycle();
831
        bool needPing = false;
832

hjk's avatar
hjk committed
833
        foreach (WatchData data, watches) {
834
            data.iname = watchHandler()->watcherName(data.exp);
835
            watchHandler()->insertData(data);
836

837 838
            if (watchHandler()->expandedINames().contains(data.iname)) {
                needPing = true;
Arvid Ephraim Picciani's avatar
Arvid Ephraim Picciani committed
839
                expandObject(data.iname, data.id);
840
            }
841 842
        }

hjk's avatar
hjk committed
843
        foreach (WatchData data, locals) {
844 845
            data.iname = "local." + data.exp;
            watchHandler()->insertData(data);
846

847 848
            if (watchHandler()->expandedINames().contains(data.iname)) {
                needPing = true;
Arvid Ephraim Picciani's avatar
Arvid Ephraim Picciani committed
849
                expandObject(data.iname, data.id);
850
            }
hjk's avatar
hjk committed
851 852
        }

853
        if (needPing) {
854
            sendPing();
855
        } else {
856
            watchHandler()->endCycle();
857
        }
858

859 860
        bool becauseOfException;
        stream >> becauseOfException;
861 862 863

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

864
        if (becauseOfException) {
865 866 867
            QString error;
            stream >> error;

Friedemann Kleint's avatar
Friedemann Kleint committed
868 869
            logString += QLatin1Char(' ');
            logString += error;
870 871
            logMessage(LogReceive, logString);

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

886 887 888 889
            if (!ideStackFrames.isEmpty()) {
                file = ideStackFrames.at(0).file;
                line = ideStackFrames.at(0).line;
                function = ideStackFrames.at(0).function;
890
            }
891

hjk's avatar
hjk committed
892 893 894 895
            BreakHandler *handler = breakHandler();
            foreach (BreakpointId id, handler->engineBreakpointIds(this)) {
                QString processedFilename = handler->fileName(id);
                if (processedFilename == file && handler->lineNumber(id) == line) {