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

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

33
#include "debuggeractions.h"
34
#include "debuggertooltip.h"
35
#include "debuggerconstants.h"
36
#include "debuggerplugin.h"
37
#include "debuggerdialogs.h"
38
#include "debuggerstringutils.h"
39
#include "debuggeruiswitcher.h"
40
#include "debuggerrunner.h"
41

42 43 44 45 46 47 48
#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
49
#include <extensionsystem/pluginmanager.h>
50
#include <projectexplorer/applicationlauncher.h>
51

52
#include <utils/environment.h>
53 54 55 56 57 58 59 60 61 62 63 64 65
#include <utils/qtcassert.h>

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

#include <QtGui/QAction>
#include <QtGui/QApplication>
#include <QtGui/QMainWindow>
#include <QtGui/QMessageBox>
#include <QtGui/QToolTip>
66
#include <QtGui/QTextDocument>
67 68

#include <QtNetwork/QTcpSocket>
69 70
#include <QtNetwork/QHostAddress>

71 72 73 74 75 76 77 78
#define DEBUG_QML 1
#if DEBUG_QML
#   define SDEBUG(s) qDebug() << s
#else
#   define SDEBUG(s)
#endif
# define XSDEBUG(s) qDebug() << s

Lasse Holmstedt's avatar
Lasse Holmstedt committed
79 80
enum {
    MaxConnectionAttempts = 50,
81
    ConnectionAttemptDefaultInterval = 200
Lasse Holmstedt's avatar
Lasse Holmstedt committed
82
};
83

84 85 86
namespace Debugger {
namespace Internal {

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

101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
} // namespace Internal

struct QmlEnginePrivate {
    explicit QmlEnginePrivate(QmlEngine *q);

    int m_ping;
    QmlAdapter *m_adapter;
    ProjectExplorer::ApplicationLauncher m_applicationLauncher;
    bool m_addedAdapterToObjectPool;
    bool m_attachToRunningExternalApp;
    bool m_hasShutdown;
};

QmlEnginePrivate::QmlEnginePrivate(QmlEngine *q) :
  m_ping(0)
, m_adapter(new QmlAdapter(q))
, m_addedAdapterToObjectPool(false)
, m_attachToRunningExternalApp(false)
, m_hasShutdown(false)
{
}

123 124 125 126 127 128
///////////////////////////////////////////////////////////////////////
//
// QmlEngine
//
///////////////////////////////////////////////////////////////////////

129
QmlEngine::QmlEngine(const DebuggerStartParameters &startParameters)
130
    : DebuggerEngine(startParameters), d(new QmlEnginePrivate(this))
131
{
Friedemann Kleint's avatar
Friedemann Kleint committed
132
    setObjectName(QLatin1String("QmlEngine"));
133 134 135 136 137 138
}

QmlEngine::~QmlEngine()
{
}

139 140
void QmlEngine::setAttachToRunningExternalApp(bool value)
{
141
    d->m_attachToRunningExternalApp = value;
142 143 144 145
}

void QmlEngine::pauseConnection()
{
146
    d->m_adapter->pauseConnection();
147 148
}

149 150 151 152 153 154 155 156 157 158
void QmlEngine::gotoLocation(const QString &fileName, int lineNumber, bool setMarker)
{
    QString processedFilename = fileName;

    if (isShadowBuildProject())
        processedFilename = fromShadowBuildFilename(fileName);

    DebuggerEngine::gotoLocation(processedFilename, lineNumber, setMarker);
}

159
void QmlEngine::gotoLocation(const Internal::StackFrame &frame, bool setMarker)
160
{
161
    Internal::StackFrame adjustedFrame = frame;
162 163 164 165 166 167
    if (isShadowBuildProject())
        adjustedFrame.file = fromShadowBuildFilename(frame.file);

    DebuggerEngine::gotoLocation(adjustedFrame, setMarker);
}

hjk's avatar
hjk committed
168
void QmlEngine::setupInferior()
169
{
hjk's avatar
hjk committed
170
    QTC_ASSERT(state() == InferiorSetupRequested, qDebug() << state());
Lasse Holmstedt's avatar
Lasse Holmstedt committed
171

172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
    if (startParameters().startMode == AttachToRemote) {
        emit remoteStartupRequested();
    } else {
        connect(&d->m_applicationLauncher, SIGNAL(processExited(int)),
                this, SLOT(disconnected()));
        connect(&d->m_applicationLauncher, SIGNAL(appendMessage(QString,bool)),
                runControl(), SLOT(emitAppendMessage(QString,bool)));
        connect(&d->m_applicationLauncher, SIGNAL(appendOutput(QString, bool)),
                runControl(), SLOT(emitAddToOutputWindow(QString, bool)));
        connect(&d->m_applicationLauncher, SIGNAL(bringToForegroundRequested(qint64)),
                runControl(), SLOT(bringApplicationToForeground(qint64)));

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

        notifyInferiorSetupOk();
    }
hjk's avatar
hjk committed
189
}
hjk's avatar
hjk committed
190

Lasse Holmstedt's avatar
Lasse Holmstedt committed
191 192 193 194 195
void QmlEngine::connectionEstablished()
{
    attemptBreakpointSynchronization();

    ExtensionSystem::PluginManager *pluginManager = ExtensionSystem::PluginManager::instance();
196
    pluginManager->addObject(d->m_adapter);
197
    pluginManager->addObject(this);
198
    d->m_addedAdapterToObjectPool = true;
Lasse Holmstedt's avatar
Lasse Holmstedt committed
199 200 201 202 203 204 205 206 207 208

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

    notifyEngineRunAndInferiorRunOk();
}

void QmlEngine::connectionStartupFailed()
{
    QMessageBox::critical(0,
                          tr("Failed to connect to debugger"),
209 210 211
                          tr("Could not connect to QML debugger server at %1:%2.")
                          .arg(startParameters().qmlServerAddress)
                          .arg(startParameters().qmlServerPort));
Lasse Holmstedt's avatar
Lasse Holmstedt committed
212 213 214
    notifyEngineRunFailed();
}

215
void QmlEngine::connectionError(QAbstractSocket::SocketError socketError)
Lasse Holmstedt's avatar
Lasse Holmstedt committed
216
{
217 218
    if (socketError ==QAbstractSocket::RemoteHostClosedError)
        plugin()->showMessage(tr("QML Debugger: Remote host closed connection."), StatusBar);
Lasse Holmstedt's avatar
Lasse Holmstedt committed
219 220
}

221 222 223

void QmlEngine::serviceConnectionError(const QString &serviceName)
{
Friedemann Kleint's avatar
Friedemann Kleint committed
224
    plugin()->showMessage(tr("QML Debugger: Could not connect to service '%1'.").arg(serviceName), StatusBar);
225 226
}

hjk's avatar
hjk committed
227 228 229
void QmlEngine::runEngine()
{
    QTC_ASSERT(state() == EngineRunRequested, qDebug() << state());
Lasse Holmstedt's avatar
Lasse Holmstedt committed
230

231 232
    if (!d->m_attachToRunningExternalApp) {
        d->m_applicationLauncher.start(ProjectExplorer::ApplicationLauncher::Gui,
233 234 235
                                    startParameters().executable,
                                    startParameters().processArgs);
    }
Lasse Holmstedt's avatar
Lasse Holmstedt committed
236

237
    d->m_adapter->beginConnection();
Lasse Holmstedt's avatar
Lasse Holmstedt committed
238
    plugin()->showMessage(tr("QML Debugger connecting..."), StatusBar);
239 240
}

241 242 243 244 245 246 247 248 249 250 251 252
void QmlEngine::handleRemoteSetupDone()
{
    notifyInferiorSetupOk();
}

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

253 254 255
void QmlEngine::shutdownInferiorAsSlave()
{
    resetLocation();
256

257 258 259 260 261 262 263 264 265 266 267 268 269
    // This can be issued in almost any state. We assume, though,
    // that at this point of time the inferior is not running anymore,
    // even if stop notification were not issued or got lost.
    if (state() == InferiorRunOk) {
        setState(InferiorStopRequested);
        setState(InferiorStopOk);
    }
    setState(InferiorShutdownRequested);
    setState(InferiorShutdownOk);
}

void QmlEngine::shutdownEngineAsSlave()
{
270
    if (d->m_hasShutdown)
271 272
        return;

273 274
    disconnect(d->m_adapter, SIGNAL(connectionStartupFailed()), this, SLOT(connectionStartupFailed()));
    d->m_adapter->closeConnection();
275

276
    if (d->m_addedAdapterToObjectPool) {
277
        ExtensionSystem::PluginManager *pluginManager = ExtensionSystem::PluginManager::instance();
278
        pluginManager->removeObject(d->m_adapter);
279
        pluginManager->removeObject(this);
280 281
    }

282
    if (d->m_attachToRunningExternalApp) {
283 284 285 286
        setState(EngineShutdownRequested, true);
        setState(EngineShutdownOk, true);
        setState(DebuggerFinished, true);
    } else {
287
        if (d->m_applicationLauncher.isRunning()) {
288
            // should only happen if engine is ill
289 290
            disconnect(&d->m_applicationLauncher, SIGNAL(processExited(int)), this, SLOT(disconnected()));
            d->m_applicationLauncher.stop();
291 292
        }
    }
293
    d->m_hasShutdown = true;
294 295
}

hjk's avatar
hjk committed
296
void QmlEngine::shutdownInferior()
297
{
298
    // don't do normal shutdown if running as slave engine
299
    if (d->m_attachToRunningExternalApp)
300 301
        return;

hjk's avatar
hjk committed
302
    QTC_ASSERT(state() == InferiorShutdownRequested, qDebug() << state());
303
    if (!d->m_applicationLauncher.isRunning()) {
Lasse Holmstedt's avatar
Lasse Holmstedt committed
304 305
        showMessage(tr("Trying to stop while process is no longer running."), LogError);
    } else {
306 307 308
        disconnect(&d->m_applicationLauncher, SIGNAL(processExited(int)), this, SLOT(disconnected()));
        if (!d->m_attachToRunningExternalApp)
            d->m_applicationLauncher.stop();
Lasse Holmstedt's avatar
Lasse Holmstedt committed
309
    }
hjk's avatar
hjk committed
310 311 312 313 314 315
    notifyInferiorShutdownOk();
}

void QmlEngine::shutdownEngine()
{
    QTC_ASSERT(state() == EngineShutdownRequested, qDebug() << state());
Lasse Holmstedt's avatar
Lasse Holmstedt committed
316

317
    shutdownEngineAsSlave();
Lasse Holmstedt's avatar
Lasse Holmstedt committed
318

319
    notifyEngineShutdownOk();
320
    plugin()->showMessage(QString(), StatusBar);
321 322
}

hjk's avatar
hjk committed
323
void QmlEngine::setupEngine()
324
{
325 326 327
    d->m_adapter->setMaxConnectionAttempts(MaxConnectionAttempts);
    d->m_adapter->setConnectionAttemptInterval(ConnectionAttemptDefaultInterval);
    connect(d->m_adapter, SIGNAL(connectionError(QAbstractSocket::SocketError)),
328
            SLOT(connectionError(QAbstractSocket::SocketError)));
329
    connect(d->m_adapter, SIGNAL(serviceConnectionError(QString)), SLOT(serviceConnectionError(QString)));
330 331
    connect(d->m_adapter, SIGNAL(connected()), SLOT(connectionEstablished()));
    connect(d->m_adapter, SIGNAL(connectionStartupFailed()), SLOT(connectionStartupFailed()));
Lasse Holmstedt's avatar
Lasse Holmstedt committed
332

333
    notifyEngineSetupOk();
334 335
}

336 337
void QmlEngine::continueInferior()
{
hjk's avatar
hjk committed
338
    QTC_ASSERT(state() == InferiorStopOk, qDebug() << state());
339 340 341 342
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
    rs << QByteArray("CONTINUE");
    sendMessage(reply);
343
    resetLocation();
hjk's avatar
hjk committed
344 345
    notifyInferiorRunRequested();
    notifyInferiorRunOk();
346 347 348 349
}

void QmlEngine::interruptInferior()
{
350 351 352 353
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
    rs << QByteArray("INTERRUPT");
    sendMessage(reply);
354
    notifyInferiorStopOk();
355 356 357 358
}

void QmlEngine::executeStep()
{
359 360 361 362
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
    rs << QByteArray("STEPINTO");
    sendMessage(reply);
hjk's avatar
hjk committed
363 364
    notifyInferiorRunRequested();
    notifyInferiorRunOk();
365 366 367 368
}

void QmlEngine::executeStepI()
{
369 370 371 372
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
    rs << QByteArray("STEPINTO");
    sendMessage(reply);
hjk's avatar
hjk committed
373 374
    notifyInferiorRunRequested();
    notifyInferiorRunOk();
375 376 377 378
}

void QmlEngine::executeStepOut()
{
379 380 381 382
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
    rs << QByteArray("STEPOUT");
    sendMessage(reply);
hjk's avatar
hjk committed
383 384
    notifyInferiorRunRequested();
    notifyInferiorRunOk();
385 386 387 388
}

void QmlEngine::executeNext()
{
389 390 391 392
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
    rs << QByteArray("STEPOVER");
    sendMessage(reply);
hjk's avatar
hjk committed
393 394
    notifyInferiorRunRequested();
    notifyInferiorRunOk();
395 396 397 398
}

void QmlEngine::executeNextI()
{
399
    SDEBUG("QmlEngine::executeNextI()");
400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424
}

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

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

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

void QmlEngine::activateFrame(int index)
{
    Q_UNUSED(index)
425 426 427 428 429 430 431

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

432
    gotoLocation(stackHandler()->frames().value(index), true);
433 434 435 436 437 438 439 440 441
}

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

void QmlEngine::attemptBreakpointSynchronization()
{
442
    Internal::BreakHandler *handler = breakHandler();
443 444 445
    //bool updateNeeded = false;
    QSet< QPair<QString, qint32> > breakList;
    for (int index = 0; index != handler->size(); ++index) {
446
        Internal::BreakpointData *data = handler->at(index);
447 448 449
        QString processedFilename = data->fileName;
        if (isShadowBuildProject())
            processedFilename = toShadowBuildFilename(data->fileName);
450
        breakList << qMakePair(processedFilename, data->lineNumber);
451 452 453 454 455 456 457 458 459 460
    }

    {
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
    rs << QByteArray("BREAKPOINTS");
    rs << breakList;
    //qDebug() << Q_FUNC_INFO << breakList;
    sendMessage(reply);
    }
461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488
}

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
//
//////////////////////////////////////////////////////////////////////

void QmlEngine::setToolTipExpression(const QPoint &mousePos, TextEditor::ITextEditor *editor, int cursorPos)
{
489 490
    // this is processed by QML inspector, which has deps to qml js editor. Makes life easier.
    emit tooltipRequested(mousePos, editor, cursorPos);
491 492 493 494 495 496 497 498
}

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

499 500
void QmlEngine::assignValueInDebugger(const Internal::WatchData *,
                                      const QString &expression, const QVariant &valueV)
501
{
502 503 504 505 506 507 508 509 510
    QRegExp inObject("@([0-9a-fA-F]+)->(.+)");
    if (inObject.exactMatch(expression)) {
        bool ok = false;
        quint64 objectId = inObject.cap(1).toULongLong(&ok, 16);
        QString property = inObject.cap(2);
        if (ok && objectId > 0 && !property.isEmpty()) {
            QByteArray reply;
            QDataStream rs(&reply, QIODevice::WriteOnly);
            rs << QByteArray("SET_PROPERTY");
511
            rs << expression.toUtf8() << objectId << property << valueV.toString();
512 513 514
            sendMessage(reply);
        }
    }
515 516
}

517
void QmlEngine::updateWatchData(const Internal::WatchData &data, const Internal::WatchUpdateFlags &)
518
{
Olivier Goffart's avatar
Olivier Goffart committed
519
//    qDebug() << "UPDATE WATCH DATA" << data.toString();
520
    //watchHandler()->rebuildModel();
521
    showStatusMessage(tr("Stopped."), 5000);
522

523
    if (!data.name.isEmpty() && data.isValueNeeded()) {
524 525 526 527 528 529 530
        QByteArray reply;
        QDataStream rs(&reply, QIODevice::WriteOnly);
        rs << QByteArray("EXEC");
        rs << data.iname << data.name;
        sendMessage(reply);
    }

531 532
    if (!data.name.isEmpty() && data.isChildrenNeeded()
            && watchHandler()->isExpandedIName(data.iname))
533 534
        expandObject(data.iname, data.objectId);

535 536 537 538 539 540 541
    {
        QByteArray reply;
        QDataStream rs(&reply, QIODevice::WriteOnly);
        rs << QByteArray("WATCH_EXPRESSIONS");
        rs << watchHandler()->watchedExpressions();
        sendMessage(reply);
    }
542 543 544

    if (!data.isSomethingNeeded())
        watchHandler()->insertData(data);
545 546
}

547 548 549 550 551 552 553 554 555
void QmlEngine::expandObject(const QByteArray& iname, quint64 objectId)
{
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
    rs << QByteArray("EXPAND");
    rs << iname << objectId;
    sendMessage(reply);
}

556 557
void QmlEngine::sendPing()
{
558
    d->m_ping++;
559 560 561
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
    rs << QByteArray("PING");
562
    rs << d->m_ping;
563 564 565
    sendMessage(reply);
}

566
namespace Internal {
567 568 569 570
DebuggerEngine *createQmlEngine(const DebuggerStartParameters &sp)
{
    return new QmlEngine(sp);
}
571
} // namespace Internal
572 573 574 575 576 577 578 579 580 581 582 583 584

unsigned QmlEngine::debuggerCapabilities() const
{
    return AddWatcherCapability;
    /*ReverseSteppingCapability | SnapshotCapability
        | AutoDerefPointersCapability | DisassemblerCapability
        | RegisterCapability | ShowMemoryCapability
        | JumpToLineCapability | ReloadModuleCapability
        | ReloadModuleSymbolsCapability | BreakOnThrowAndCatchCapability
        | ReturnFromFunctionCapability
        | CreateFullBacktraceCapability
        | WatchpointCapability
        | AddWatcherCapability;*/
585 586 587 588 589 590 591 592 593 594
}

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

    QByteArray command;
    stream >> command;

595
    showMessage(QLatin1String("RECEIVED RESPONSE: ") + Internal::quoteUnprintableLatin1(message));
596
    if (command == "STOPPED") {
597 598 599
        if (state() == InferiorRunOk) {
            notifyInferiorSpontaneousStop();
        }
600

601
        QList<QPair<QString, QPair<QString, qint32> > > backtrace;
602 603
        QList<Internal::WatchData> watches;
        QList<Internal::WatchData> locals;
604
        stream >> backtrace >> watches >> locals;
605

606
        Internal::StackFrames stackFrames;
607 608
        typedef QPair<QString, QPair<QString, qint32> > Iterator;
        foreach (const Iterator &it, backtrace) {
609
            Internal::StackFrame frame;
610 611 612
            frame.file = it.second.first;
            frame.line = it.second.second;
            frame.function = it.first;
613
            stackFrames.append(frame);
614 615 616 617 618 619
        }

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

        watchHandler()->beginCycle();
620
        bool needPing = false;
621

622
        foreach (Internal::WatchData data, watches) {
623
            data.iname = watchHandler()->watcherName(data.exp);
624
            watchHandler()->insertData(data);
625

626 627
            if (watchHandler()->expandedINames().contains(data.iname)) {
                needPing = true;
628
                expandObject(data.iname, data.objectId);
629
            }
630 631
        }

632
        foreach (Internal::WatchData data, locals) {
633 634
            data.iname = "local." + data.exp;
            watchHandler()->insertData(data);
635

636 637
            if (watchHandler()->expandedINames().contains(data.iname)) {
                needPing = true;
638
                expandObject(data.iname, data.objectId);
639
            }
hjk's avatar
hjk committed
640 641
        }

642 643 644 645
        if (needPing)
            sendPing();
        else
            watchHandler()->endCycle();
646

647 648 649
        bool becauseOfException;
        stream >> becauseOfException;
        if (becauseOfException) {
650 651 652
            QString error;
            stream >> error;

653 654 655
            QString msg =
                tr("<p>An Uncaught Exception occured in <i>%1</i>:</p><p>%2</p>")
                    .arg(stackFrames.value(0).file, Qt::escape(error));
656
            showMessageBox(QMessageBox::Information, tr("Uncaught Exception"), msg);
657 658 659 660 661 662 663 664 665 666 667 668 669 670 671
        } else {
            //
            // Make breakpoint non-pending
            //
            QString file;
            int line = -1;

            if (!stackFrames.isEmpty()) {
                file = stackFrames.at(0).file;
                line = stackFrames.at(0).line;

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

673 674 675 676
            Internal::BreakHandler *handler = breakHandler();
            for (int index = 0; index != handler->size(); ++index) {
                Internal::BreakpointData *data = handler->at(index);
                QString processedFilename = data->fileName;
677

678 679 680 681 682 683 684
                if (processedFilename == file
                        && data->lineNumber == line) {
                    data->pending = false;
                    data->updateMarker();
                }
            }
        }
685
    } else if (command == "RESULT") {
686
        Internal::WatchData data;
687 688 689
        QByteArray iname;
        stream >> iname >> data;
        data.iname = iname;
690 691 692 693 694 695 696
        if (iname.startsWith("watch.")) {
            watchHandler()->insertData(data);
        } else if(iname == "console") {
            plugin()->showMessage(data.value, ScriptConsoleOutput);
        } else {
            qWarning() << "QmlEngine: Unexcpected result: " << iname << data.value;
        }
697
    } else if (command == "EXPANDED") {
698
        QList<Internal::WatchData> result;
699 700
        QByteArray iname;
        stream >> iname >> result;
701
        bool needPing = false;
702
        foreach (Internal::WatchData data, result) {
703 704 705
            data.iname = iname + '.' + data.exp;
            watchHandler()->insertData(data);

706 707
            if (watchHandler()->expandedINames().contains(data.iname)) {
                needPing = true;
708
                expandObject(data.iname, data.objectId);
709
            }
710
        }
711 712
        if (needPing)
            sendPing();
713
    } else if (command == "LOCALS") {
714
        QList<Internal::WatchData> locals;
715 716 717
        int frameId;
        stream >> frameId >> locals;
        watchHandler()->beginCycle();
718
        bool needPing = false;
719
        foreach (Internal::WatchData data, locals) {
720 721
            data.iname = "local." + data.exp;
            watchHandler()->insertData(data);
722 723
            if (watchHandler()->expandedINames().contains(data.iname)) {
                needPing = true;
724
                expandObject(data.iname, data.objectId);
725
            }
726
        }
727 728 729 730 731 732 733 734
        if (needPing)
            sendPing();
        else
            watchHandler()->endCycle();

    } else if (command == "PONG") {
        int ping;
        stream >> ping;
735
        if (ping == d->m_ping)
736
            watchHandler()->endCycle();
737 738 739 740 741 742
    } else {
        qDebug() << Q_FUNC_INFO << "Unknown command: " << command;
    }

}

743 744
void QmlEngine::disconnected()
{
Lasse Holmstedt's avatar
Lasse Holmstedt committed
745
    plugin()->showMessage(tr("QML Debugger disconnected."), StatusBar);
746 747 748
    notifyInferiorExited();
}

749 750 751 752 753 754 755 756 757
void QmlEngine::executeDebuggerCommand(const QString& command)
{
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
    rs << QByteArray("EXEC");
    rs << QByteArray("console") << command;
    sendMessage(reply);
}

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 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824
bool QmlEngine::isShadowBuildProject() const
{
    if (!startParameters().projectBuildDir.isEmpty()
        && (startParameters().projectDir != startParameters().projectBuildDir))
    {
        return true;
    }
    return false;
}

QString QmlEngine::qmlImportPath() const
{
    QString result;
    const QString qmlImportPathPrefix("QML_IMPORT_PATH=");
    QStringList env = startParameters().environment;
    foreach(const QString &envStr, env) {
        if (envStr.startsWith(qmlImportPathPrefix)) {
            result = envStr.mid(qmlImportPathPrefix.length());
            break;
        }
    }
    return result;
}

QString QmlEngine::toShadowBuildFilename(const QString &filename) const
{
    QString newFilename = filename;
    QString importPath = qmlImportPath();

    newFilename = mangleFilenamePaths(filename, startParameters().projectDir, startParameters().projectBuildDir);
    if (newFilename == filename && !importPath.isEmpty()) {
        newFilename = mangleFilenamePaths(filename, startParameters().projectDir, importPath);
    }

    return newFilename;
}

QString QmlEngine::mangleFilenamePaths(const QString &filename, const QString &oldBasePath, const QString &newBasePath) const
{
    QDir oldBaseDir(oldBasePath);
    QDir newBaseDir(newBasePath);
    QFileInfo fileInfo(filename);

    if (oldBaseDir.exists() && newBaseDir.exists() && fileInfo.exists()) {
        if (fileInfo.absoluteFilePath().startsWith(oldBaseDir.canonicalPath())) {
            QString fileRelativePath = fileInfo.canonicalFilePath().mid(oldBasePath.length());
            QFileInfo projectFile(newBaseDir.canonicalPath() + QLatin1Char('/') + fileRelativePath);

            if (projectFile.exists())
                return projectFile.canonicalFilePath();
        }
    }
    return filename;
}

QString QmlEngine::fromShadowBuildFilename(const QString &filename) const
{
    QString newFilename = filename;
    QString importPath = qmlImportPath();

    newFilename = mangleFilenamePaths(filename, startParameters().projectBuildDir, startParameters().projectDir);
    if (newFilename == filename && !importPath.isEmpty()) {
        newFilename = mangleFilenamePaths(filename, startParameters().projectBuildDir, importPath);
    }

    return newFilename;
}
825

826
} // namespace Debugger
827