qmlengine.cpp 32.8 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 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
    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) {
259 260 261 262 263 264 265
    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");
266
        // fall through
267 268 269
    }
    default:
        notifyEngineRunFailed();
Kai Koehne's avatar
Kai Koehne committed
270
        break;
271
    }
Lasse Holmstedt's avatar
Lasse Holmstedt committed
272 273
}

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

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

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

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

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

            connect(infoBox, SIGNAL(finished(int)),
336
                    this, SLOT(wrongSetupMessageBoxFinished(int)));
337 338 339

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

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

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

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

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

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

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

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

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

void QmlEngine::shutdownEngine()
{
421
    closeConnection();
Lasse Holmstedt's avatar
Lasse Holmstedt committed
422

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

426
    notifyEngineShutdownOk();
427 428
    if (!isSlaveEngine())
        showMessage(QString(), StatusBar);
429 430
}

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

443
    notifyEngineSetupOk();
444 445
}

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

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

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

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

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

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

void QmlEngine::executeNextI()
{
521
    SDEBUG("QmlEngine::executeNextI()");
522 523
}

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

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

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

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

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

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

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

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

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

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

602
    sendMessage(reply);
603 604
}

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

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

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

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

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

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

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

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

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

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

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

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

746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777
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;
}

778 779 780 781 782 783 784 785
void QmlEngine::messageReceived(const QByteArray &message)
{
    QByteArray rwData = message;
    QDataStream stream(&rwData, QIODevice::ReadOnly);

    QByteArray command;
    stream >> command;

786
    if (command == "STOPPED") {
787
        //qDebug() << command << this << state();
hjk's avatar
hjk committed
788
        if (state() == InferiorRunOk)
789
            notifyInferiorSpontaneousStop();
790

hjk's avatar
hjk committed
791
        QString logString = QString::fromLatin1(command);
792

793
        JSAgentStackFrames stackFrames;
hjk's avatar
hjk committed
794 795
        QList<WatchData> watches;
        QList<WatchData> locals;
796
        stream >> stackFrames >> watches >> locals;
797

798 799
        logString += QString::fromLatin1(" (%1 stack frames) (%2 watches)  (%3 locals)").
                     arg(stackFrames.size()).arg(watches.size()).arg(locals.size());
800

801 802 803 804 805 806 807 808 809 810
        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;
        }
811

812 813 814
        if (ideStackFrames.size() && ideStackFrames.back().function == "<global>")
            ideStackFrames.takeLast();
        stackHandler()->setFrames(ideStackFrames);
815 816

        watchHandler()->beginCycle();
817
        bool needPing = false;
818

hjk's avatar
hjk committed
819
        foreach (WatchData data, watches) {
820
            data.iname = watchHandler()->watcherName(data.exp);
821
            watchHandler()->insertData(data);
822

823 824
            if (watchHandler()->expandedINames().contains(data.iname)) {
                needPing = true;
Arvid Ephraim Picciani's avatar
Arvid Ephraim Picciani committed
825
                expandObject(data.iname, data.id);
826
            }
827 828
        }

hjk's avatar
hjk committed
829
        foreach (WatchData data, locals) {
830 831
            data.iname = "local." + data.exp;
            watchHandler()->insertData(data);
832

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

839
        if (needPing) {
840
            sendPing();
841
        } else {
842
            watchHandler()->endCycle();
843
        }
844

845 846
        bool becauseOfException;
        stream >> becauseOfException;
847 848 849

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

850
        if (becauseOfException) {
851 852 853
            QString error;
            stream >> error;

Friedemann Kleint's avatar
Friedemann Kleint committed
854 855
            logString += QLatin1Char(' ');
            logString += error;
856 857
            logMessage(LogReceive, logString);

858
            QString msg = stackFrames.isEmpty()
Friedemann Kleint's avatar
Friedemann Kleint committed
859
                ? tr("<p>An uncaught exception occurred:</p><p>%1</p>")
860
                    .arg(Qt::escape(error))
Friedemann Kleint's avatar
Friedemann Kleint committed
861
                : tr("<p>An uncaught exception occurred in <i>%1</i>:</p><p>%2</p>")
862
                    .arg(stackFrames.value(0).fileUrl, Qt::escape(error));
863
            showMessageBox(QMessageBox::Information, tr("Uncaught Exception"), msg);
864 865 866 867 868
        } else {
            //
            // Make breakpoint non-pending
            //
            QString file;
869
            QString function;
870 871
            int line = -1;

872 873 874 875
            if (!ideStackFrames.isEmpty()) {
                file = ideStackFrames.at(0).file;
                line = ideStackFrames.at(0).line;
                function = ideStackFrames.at(0).function;
876
            }
877

hjk's avatar
hjk committed
878 879 880 881
            BreakHandler *handler = breakHandler();
            foreach (BreakpointId id, handler->engineBreakpointIds(this)) {
                QString processedFilename = handler->fileName(id);
                if (processedFilename == file && handler->lineNumber(id) == line) {
882
                    QTC_ASSERT(handler->state(id) == BreakpointInserted,/**/);
hjk's avatar
hjk committed
883
                    BreakpointResponse br = handler->response(id);
884 885 886
                    br.fileName = file;
                    br.lineNumber = line;
                    br.functionName = function;
hjk's avatar
hjk committed
887
                    handler->setResponse(id, br);
888 889
                }
            }
890 891

            logMessage(LogReceive, logString);
892
        }
893

894 895
        if (!ideStackFrames.isEmpty())
            gotoLocation(ideStackFrames.value(0));
896

897
    } else if (command == "RESULT") {
hjk's avatar
hjk committed
898
        WatchData data;
899 900
        QByteArray iname;
        stream >> iname >> data;
901 902 903

        logMessage(LogReceive, QString("%1 %2 %3").arg(QString(command),
                                                             QString(iname), QString(data.value)));
904
        data.iname = iname;
905 906 907
        if (iname.startsWith("watch.")) {
            watchHandler()->insertData(data);
        } else if(iname == "console") {
908
            showMessage(data.value, ScriptConsoleOutput);
909 910 911
        } else {
            qWarning() << "QmlEngine: Unexcpected result: " << iname << data.value;
        }
912
    } else if (command == "EXPANDED") {
hjk's avatar
hjk committed
913
        QList<WatchData> result;
914 915
        QByteArray iname;
        stream >> iname >> result;
916 917 918

        logMessage(LogReceive, QString("%1 %2 (%3 x watchdata)").arg(
                             QString(command), QString(iname), QString::number(result.size())));