qmlengine.cpp 25.6 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
    // 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
        QString processedFilename = data->fileName;
452 453 454 455 456 457
#ifdef Q_OS_MACX
        // Qt Quick Applications by default copy the qml directory to buildDir()/X.app/Contents/Resources
        const QString applicationBundleDir
                = QFileInfo(startParameters().executable).absolutePath() + "/../..";
        processedFilename = mangleFilenamePaths(data->fileName, startParameters().projectDir, applicationBundleDir + "/Contents/Resources");
#endif
458
        if (isShadowBuildProject())
459 460
            processedFilename = toShadowBuildFilename(processedFilename);

461
        breakList << qMakePair(processedFilename, data->lineNumber);
462 463 464 465 466 467 468 469 470 471
    }

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

474 475 476 477 478
bool QmlEngine::acceptsBreakpoint(const Internal::BreakpointData *br)
{
    return (br->fileName.endsWith(QLatin1String("qml")) || br->fileName.endsWith(QLatin1String("js")));
}

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

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

515 516
void QmlEngine::assignValueInDebugger(const Internal::WatchData *,
                                      const QString &expression, const QVariant &valueV)
517
{
518 519 520 521 522 523 524 525 526
    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");
527
            rs << expression.toUtf8() << objectId << property << valueV.toString();
528 529 530
            sendMessage(reply);
        }
    }
531 532
}

533
void QmlEngine::updateWatchData(const Internal::WatchData &data, const Internal::WatchUpdateFlags &)
534
{
Olivier Goffart's avatar
Olivier Goffart committed
535
//    qDebug() << "UPDATE WATCH DATA" << data.toString();
536
    //watchHandler()->rebuildModel();
537
    showStatusMessage(tr("Stopped."), 5000);
538

539
    if (!data.name.isEmpty() && data.isValueNeeded()) {
540 541 542 543 544 545 546
        QByteArray reply;
        QDataStream rs(&reply, QIODevice::WriteOnly);
        rs << QByteArray("EXEC");
        rs << data.iname << data.name;
        sendMessage(reply);
    }

547 548
    if (!data.name.isEmpty() && data.isChildrenNeeded()
            && watchHandler()->isExpandedIName(data.iname))
549 550
        expandObject(data.iname, data.objectId);

551 552 553 554 555 556 557
    {
        QByteArray reply;
        QDataStream rs(&reply, QIODevice::WriteOnly);
        rs << QByteArray("WATCH_EXPRESSIONS");
        rs << watchHandler()->watchedExpressions();
        sendMessage(reply);
    }
558 559 560

    if (!data.isSomethingNeeded())
        watchHandler()->insertData(data);
561 562
}

563 564 565 566 567 568 569 570 571
void QmlEngine::expandObject(const QByteArray& iname, quint64 objectId)
{
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
    rs << QByteArray("EXPAND");
    rs << iname << objectId;
    sendMessage(reply);
}

572 573
void QmlEngine::sendPing()
{
574
    d->m_ping++;
575 576 577
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
    rs << QByteArray("PING");
578
    rs << d->m_ping;
579 580 581
    sendMessage(reply);
}

582
namespace Internal {
583 584 585 586
DebuggerEngine *createQmlEngine(const DebuggerStartParameters &sp)
{
    return new QmlEngine(sp);
}
587
} // namespace Internal
588 589 590 591 592 593 594 595 596 597 598 599 600

unsigned QmlEngine::debuggerCapabilities() const
{
    return AddWatcherCapability;
    /*ReverseSteppingCapability | SnapshotCapability
        | AutoDerefPointersCapability | DisassemblerCapability
        | RegisterCapability | ShowMemoryCapability
        | JumpToLineCapability | ReloadModuleCapability
        | ReloadModuleSymbolsCapability | BreakOnThrowAndCatchCapability
        | ReturnFromFunctionCapability
        | CreateFullBacktraceCapability
        | WatchpointCapability
        | AddWatcherCapability;*/
601 602 603 604 605 606 607 608 609 610
}

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

    QByteArray command;
    stream >> command;

611
    showMessage(QLatin1String("RECEIVED RESPONSE: ") + Internal::quoteUnprintableLatin1(message));
612
    if (command == "STOPPED") {
613 614 615
        if (state() == InferiorRunOk) {
            notifyInferiorSpontaneousStop();
        }
616

617
        QList<QPair<QString, QPair<QString, qint32> > > backtrace;
618 619
        QList<Internal::WatchData> watches;
        QList<Internal::WatchData> locals;
620
        stream >> backtrace >> watches >> locals;
621

622
        Internal::StackFrames stackFrames;
623 624
        typedef QPair<QString, QPair<QString, qint32> > Iterator;
        foreach (const Iterator &it, backtrace) {
625
            Internal::StackFrame frame;
626 627 628
            frame.file = it.second.first;
            frame.line = it.second.second;
            frame.function = it.first;
629
            stackFrames.append(frame);
630 631 632 633 634 635
        }

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

        watchHandler()->beginCycle();
636
        bool needPing = false;
637

638
        foreach (Internal::WatchData data, watches) {
639
            data.iname = watchHandler()->watcherName(data.exp);
640
            watchHandler()->insertData(data);
641

642 643
            if (watchHandler()->expandedINames().contains(data.iname)) {
                needPing = true;
644
                expandObject(data.iname, data.objectId);
645
            }
646 647
        }

648
        foreach (Internal::WatchData data, locals) {
649 650
            data.iname = "local." + data.exp;
            watchHandler()->insertData(data);
651

652 653
            if (watchHandler()->expandedINames().contains(data.iname)) {
                needPing = true;
654
                expandObject(data.iname, data.objectId);
655
            }
hjk's avatar
hjk committed
656 657
        }

658 659 660 661
        if (needPing)
            sendPing();
        else
            watchHandler()->endCycle();
662

663 664 665
        bool becauseOfException;
        stream >> becauseOfException;
        if (becauseOfException) {
666 667 668
            QString error;
            stream >> error;

669 670 671
            QString msg =
                tr("<p>An Uncaught Exception occured in <i>%1</i>:</p><p>%2</p>")
                    .arg(stackFrames.value(0).file, Qt::escape(error));
672
            showMessageBox(QMessageBox::Information, tr("Uncaught Exception"), msg);
673 674 675 676 677 678 679 680 681 682 683 684 685 686 687
        } 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);
                }
            }
688

689 690 691 692
            Internal::BreakHandler *handler = breakHandler();
            for (int index = 0; index != handler->size(); ++index) {
                Internal::BreakpointData *data = handler->at(index);
                QString processedFilename = data->fileName;
693

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

724 725
            if (watchHandler()->expandedINames().contains(data.iname)) {
                needPing = true;
726
                expandObject(data.iname, data.objectId);
727
            }
728
        }
729 730
        if (needPing)
            sendPing();
731
    } else if (command == "LOCALS") {
732
        QList<Internal::WatchData> locals;
733 734 735
        int frameId;
        stream >> frameId >> locals;
        watchHandler()->beginCycle();
736
        bool needPing = false;
737
        foreach (Internal::WatchData data, locals) {
738 739
            data.iname = "local." + data.exp;
            watchHandler()->insertData(data);
740 741
            if (watchHandler()->expandedINames().contains(data.iname)) {
                needPing = true;
742
                expandObject(data.iname, data.objectId);
743
            }
744
        }
745 746 747 748 749 750 751 752
        if (needPing)
            sendPing();
        else
            watchHandler()->endCycle();

    } else if (command == "PONG") {
        int ping;
        stream >> ping;
753
        if (ping == d->m_ping)
754
            watchHandler()->endCycle();
755 756 757 758 759 760
    } else {
        qDebug() << Q_FUNC_INFO << "Unknown command: " << command;
    }

}

761 762
void QmlEngine::disconnected()
{
Lasse Holmstedt's avatar
Lasse Holmstedt committed
763
    plugin()->showMessage(tr("QML Debugger disconnected."), StatusBar);
764 765 766
    notifyInferiorExited();
}

767 768 769 770 771 772 773 774 775
void QmlEngine::executeDebuggerCommand(const QString& command)
{
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
    rs << QByteArray("EXEC");
    rs << QByteArray("console") << command;
    sendMessage(reply);
}

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
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())) {
821
            QString fileRelativePath = fileInfo.canonicalFilePath().mid(oldBaseDir.canonicalPath().length());
822 823 824 825 826 827 828 829 830 831 832 833 834 835
            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();

836 837 838 839 840 841 842 843
#ifdef Q_OS_MACX
    // Qt Quick Applications by default copy the qml directory to buildDir()/X.app/Contents/Resources
    const QString applicationBundleDir
                = QFileInfo(startParameters().executable).absolutePath() + "/../..";
    newFilename = mangleFilenamePaths(newFilename, applicationBundleDir + "/Contents/Resources", startParameters().projectDir);
#endif
    newFilename = mangleFilenamePaths(newFilename, startParameters().projectBuildDir, startParameters().projectDir);

844 845 846 847 848 849
    if (newFilename == filename && !importPath.isEmpty()) {
        newFilename = mangleFilenamePaths(filename, startParameters().projectBuildDir, importPath);
    }

    return newFilename;
}
850

851
} // namespace Debugger
852