qmlengine.cpp 24.8 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;
Arvid Ephraim Picciani's avatar
Arvid Ephraim Picciani committed
93
    s >> data.exp >> data.name >> value >> type >> hasChildren >> data.id;
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
    // 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);
263 264 265 266 267 268
        setState(InferiorShutdownRequested);
        setState(InferiorShutdownOk);
    } else {
        // force
        setState(InferiorShutdownRequested, true);
        setState(InferiorShutdownOk);
269 270 271 272 273
    }
}

void QmlEngine::shutdownEngineAsSlave()
{
274
    if (d->m_hasShutdown)
275 276
        return;

277 278
    disconnect(d->m_adapter, SIGNAL(connectionStartupFailed()), this, SLOT(connectionStartupFailed()));
    d->m_adapter->closeConnection();
279

280
    if (d->m_addedAdapterToObjectPool) {
281
        ExtensionSystem::PluginManager *pluginManager = ExtensionSystem::PluginManager::instance();
282
        pluginManager->removeObject(d->m_adapter);
283
        pluginManager->removeObject(this);
284 285
    }

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

hjk's avatar
hjk committed
300
void QmlEngine::shutdownInferior()
301
{
302
    // don't do normal shutdown if running as slave engine
303
    if (d->m_attachToRunningExternalApp)
304 305
        return;

hjk's avatar
hjk committed
306
    QTC_ASSERT(state() == InferiorShutdownRequested, qDebug() << state());
307
    if (!d->m_applicationLauncher.isRunning()) {
Lasse Holmstedt's avatar
Lasse Holmstedt committed
308 309
        showMessage(tr("Trying to stop while process is no longer running."), LogError);
    } else {
310 311 312
        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
313
    }
hjk's avatar
hjk committed
314 315 316 317 318 319
    notifyInferiorShutdownOk();
}

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

321
    shutdownEngineAsSlave();
Lasse Holmstedt's avatar
Lasse Holmstedt committed
322

323
    notifyEngineShutdownOk();
324
    plugin()->showMessage(QString(), StatusBar);
325 326
}

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

337
    notifyEngineSetupOk();
338 339
}

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

void QmlEngine::interruptInferior()
{
354 355 356 357
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
    rs << QByteArray("INTERRUPT");
    sendMessage(reply);
358
    notifyInferiorStopOk();
359 360 361 362
}

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

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

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

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

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

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)
429 430 431 432 433 434 435

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

436
    gotoLocation(stackHandler()->frames().value(index), true);
437 438 439 440 441 442 443 444 445
}

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

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

    {
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
    rs << QByteArray("BREAKPOINTS");
    rs << breakList;
    //qDebug() << Q_FUNC_INFO << breakList;
    sendMessage(reply);
    }
465 466
}

467 468 469 470 471
bool QmlEngine::acceptsBreakpoint(const Internal::BreakpointData *br)
{
    return (br->fileName.endsWith(QLatin1String("qml")) || br->fileName.endsWith(QLatin1String("js")));
}

472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497
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)
{
498 499
    // this is processed by QML inspector, which has deps to qml js editor. Makes life easier.
    emit tooltipRequested(mousePos, editor, cursorPos);
500 501 502 503 504 505 506 507
}

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

508 509
void QmlEngine::assignValueInDebugger(const Internal::WatchData *,
                                      const QString &expression, const QVariant &valueV)
510
{
511 512 513 514 515 516 517 518 519
    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");
520
            rs << expression.toUtf8() << objectId << property << valueV.toString();
521 522 523
            sendMessage(reply);
        }
    }
524 525
}

526
void QmlEngine::updateWatchData(const Internal::WatchData &data, const Internal::WatchUpdateFlags &)
527
{
Olivier Goffart's avatar
Olivier Goffart committed
528
//    qDebug() << "UPDATE WATCH DATA" << data.toString();
529
    //watchHandler()->rebuildModel();
530
    showStatusMessage(tr("Stopped."), 5000);
531

532
    if (!data.name.isEmpty() && data.isValueNeeded()) {
533 534 535 536 537 538 539
        QByteArray reply;
        QDataStream rs(&reply, QIODevice::WriteOnly);
        rs << QByteArray("EXEC");
        rs << data.iname << data.name;
        sendMessage(reply);
    }

540 541
    if (!data.name.isEmpty() && data.isChildrenNeeded()
            && watchHandler()->isExpandedIName(data.iname))
Arvid Ephraim Picciani's avatar
Arvid Ephraim Picciani committed
542
        expandObject(data.iname, data.id);
543

544 545 546 547 548 549 550
    {
        QByteArray reply;
        QDataStream rs(&reply, QIODevice::WriteOnly);
        rs << QByteArray("WATCH_EXPRESSIONS");
        rs << watchHandler()->watchedExpressions();
        sendMessage(reply);
    }
551 552 553

    if (!data.isSomethingNeeded())
        watchHandler()->insertData(data);
554 555
}

556 557 558 559 560 561 562 563 564
void QmlEngine::expandObject(const QByteArray& iname, quint64 objectId)
{
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
    rs << QByteArray("EXPAND");
    rs << iname << objectId;
    sendMessage(reply);
}

565 566
void QmlEngine::sendPing()
{
567
    d->m_ping++;
568 569 570
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
    rs << QByteArray("PING");
571
    rs << d->m_ping;
572 573 574
    sendMessage(reply);
}

575
namespace Internal {
576 577 578 579
DebuggerEngine *createQmlEngine(const DebuggerStartParameters &sp)
{
    return new QmlEngine(sp);
}
580
} // namespace Internal
581 582 583 584 585 586 587 588 589 590 591 592 593

unsigned QmlEngine::debuggerCapabilities() const
{
    return AddWatcherCapability;
    /*ReverseSteppingCapability | SnapshotCapability
        | AutoDerefPointersCapability | DisassemblerCapability
        | RegisterCapability | ShowMemoryCapability
        | JumpToLineCapability | ReloadModuleCapability
        | ReloadModuleSymbolsCapability | BreakOnThrowAndCatchCapability
        | ReturnFromFunctionCapability
        | CreateFullBacktraceCapability
        | WatchpointCapability
        | AddWatcherCapability;*/
594 595 596 597 598 599 600 601 602 603
}

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

    QByteArray command;
    stream >> command;

604
    showMessage(QLatin1String("RECEIVED RESPONSE: ") + Internal::quoteUnprintableLatin1(message));
605
    if (command == "STOPPED") {
606 607 608
        if (state() == InferiorRunOk) {
            notifyInferiorSpontaneousStop();
        }
609

610
        QList<QPair<QString, QPair<QString, qint32> > > backtrace;
611 612
        QList<Internal::WatchData> watches;
        QList<Internal::WatchData> locals;
613
        stream >> backtrace >> watches >> locals;
614

615
        Internal::StackFrames stackFrames;
616 617
        typedef QPair<QString, QPair<QString, qint32> > Iterator;
        foreach (const Iterator &it, backtrace) {
618
            Internal::StackFrame frame;
619 620 621
            frame.file = it.second.first;
            frame.line = it.second.second;
            frame.function = it.first;
622
            stackFrames.append(frame);
623 624 625 626 627 628
        }

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

        watchHandler()->beginCycle();
629
        bool needPing = false;
630

631
        foreach (Internal::WatchData data, watches) {
632
            data.iname = watchHandler()->watcherName(data.exp);
633
            watchHandler()->insertData(data);
634

635 636
            if (watchHandler()->expandedINames().contains(data.iname)) {
                needPing = true;
Arvid Ephraim Picciani's avatar
Arvid Ephraim Picciani committed
637
                expandObject(data.iname, data.id);
638
            }
639 640
        }

641
        foreach (Internal::WatchData data, locals) {
642 643
            data.iname = "local." + data.exp;
            watchHandler()->insertData(data);
644

645 646
            if (watchHandler()->expandedINames().contains(data.iname)) {
                needPing = true;
Arvid Ephraim Picciani's avatar
Arvid Ephraim Picciani committed
647
                expandObject(data.iname, data.id);
648
            }
hjk's avatar
hjk committed
649 650
        }

651 652 653 654
        if (needPing)
            sendPing();
        else
            watchHandler()->endCycle();
655

656 657 658
        bool becauseOfException;
        stream >> becauseOfException;
        if (becauseOfException) {
659 660 661
            QString error;
            stream >> error;

662 663 664
            QString msg =
                tr("<p>An Uncaught Exception occured in <i>%1</i>:</p><p>%2</p>")
                    .arg(stackFrames.value(0).file, Qt::escape(error));
665
            showMessageBox(QMessageBox::Information, tr("Uncaught Exception"), msg);
666 667 668 669 670 671 672 673 674 675 676 677 678 679 680
        } 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);
                }
            }
681

682 683 684 685
            Internal::BreakHandler *handler = breakHandler();
            for (int index = 0; index != handler->size(); ++index) {
                Internal::BreakpointData *data = handler->at(index);
                QString processedFilename = data->fileName;
686

687 688 689
                if (processedFilename == file
                        && data->lineNumber == line) {
                    data->pending = false;
690 691
                    data->bpFileName = file;
                    data->bpLineNumber = line;
692 693 694 695
                    data->updateMarker();
                }
            }
        }
696
    } else if (command == "RESULT") {
697
        Internal::WatchData data;
698 699 700
        QByteArray iname;
        stream >> iname >> data;
        data.iname = iname;
701 702 703 704 705 706 707
        if (iname.startsWith("watch.")) {
            watchHandler()->insertData(data);
        } else if(iname == "console") {
            plugin()->showMessage(data.value, ScriptConsoleOutput);
        } else {
            qWarning() << "QmlEngine: Unexcpected result: " << iname << data.value;
        }
708
    } else if (command == "EXPANDED") {
709
        QList<Internal::WatchData> result;
710 711
        QByteArray iname;
        stream >> iname >> result;
712
        bool needPing = false;
713
        foreach (Internal::WatchData data, result) {
714 715 716
            data.iname = iname + '.' + data.exp;
            watchHandler()->insertData(data);

717 718
            if (watchHandler()->expandedINames().contains(data.iname)) {
                needPing = true;
Arvid Ephraim Picciani's avatar
Arvid Ephraim Picciani committed
719
                expandObject(data.iname, data.id);
720
            }
721
        }
722 723
        if (needPing)
            sendPing();
724
    } else if (command == "LOCALS") {
725
        QList<Internal::WatchData> locals;
726 727 728
        int frameId;
        stream >> frameId >> locals;
        watchHandler()->beginCycle();
729
        bool needPing = false;
730
        foreach (Internal::WatchData data, locals) {
731 732
            data.iname = "local." + data.exp;
            watchHandler()->insertData(data);
733 734
            if (watchHandler()->expandedINames().contains(data.iname)) {
                needPing = true;
Arvid Ephraim Picciani's avatar
Arvid Ephraim Picciani committed
735
                expandObject(data.iname, data.id);
736
            }
737
        }
738 739 740 741 742 743 744 745
        if (needPing)
            sendPing();
        else
            watchHandler()->endCycle();

    } else if (command == "PONG") {
        int ping;
        stream >> ping;
746
        if (ping == d->m_ping)
747
            watchHandler()->endCycle();
748 749 750 751 752 753
    } else {
        qDebug() << Q_FUNC_INFO << "Unknown command: " << command;
    }

}

754 755
void QmlEngine::disconnected()
{
Lasse Holmstedt's avatar
Lasse Holmstedt committed
756
    plugin()->showMessage(tr("QML Debugger disconnected."), StatusBar);
757 758 759
    notifyInferiorExited();
}

760 761 762 763 764 765 766 767 768
void QmlEngine::executeDebuggerCommand(const QString& command)
{
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
    rs << QByteArray("EXEC");
    rs << QByteArray("console") << command;
    sendMessage(reply);
}

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 825 826 827 828 829 830 831 832 833 834 835
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;
}
836

837
} // namespace Debugger
838