qmlengine.cpp 23.3 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 "debuggertooltip.h"
34
#include "debuggerconstants.h"
35
#include "debuggerplugin.h"
36
#include "debuggerdialogs.h"
37
#include "debuggerstringutils.h"
38
#include "debuggeruiswitcher.h"
39
#include "debuggerrunner.h"
40

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

52 53 54 55 56 57 58 59 60 61 62 63 64
#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>
65
#include <QtGui/QTextDocument>
66 67

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

70 71 72 73 74 75 76 77
#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
78 79
enum {
    MaxConnectionAttempts = 50,
80
    ConnectionAttemptDefaultInterval = 200
Lasse Holmstedt's avatar
Lasse Holmstedt committed
81
};
82

83 84 85
namespace Debugger {
namespace Internal {

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

100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
} // 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)
{
}

122 123 124 125 126 127
///////////////////////////////////////////////////////////////////////
//
// QmlEngine
//
///////////////////////////////////////////////////////////////////////

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

QmlEngine::~QmlEngine()
{
}

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

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

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

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

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

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

    DebuggerEngine::gotoLocation(adjustedFrame, setMarker);
}

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

171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
    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
188
}
hjk's avatar
hjk committed
189

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

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

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

    notifyEngineRunAndInferiorRunOk();
}

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

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

hjk's avatar
hjk committed
220 221 222
void QmlEngine::runEngine()
{
    QTC_ASSERT(state() == EngineRunRequested, qDebug() << state());
Lasse Holmstedt's avatar
Lasse Holmstedt committed
223

224 225
    if (!d->m_attachToRunningExternalApp) {
        d->m_applicationLauncher.start(ProjectExplorer::ApplicationLauncher::Gui,
226 227 228
                                    startParameters().executable,
                                    startParameters().processArgs);
    }
Lasse Holmstedt's avatar
Lasse Holmstedt committed
229

230
    d->m_adapter->beginConnection();
Lasse Holmstedt's avatar
Lasse Holmstedt committed
231
    plugin()->showMessage(tr("QML Debugger connecting..."), StatusBar);
232 233
}

234 235 236 237 238 239 240 241 242 243 244 245
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();
}

246 247 248
void QmlEngine::shutdownInferiorAsSlave()
{
    resetLocation();
249

250 251 252 253 254 255 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);
    }
    setState(InferiorShutdownRequested);
    setState(InferiorShutdownOk);
}

void QmlEngine::shutdownEngineAsSlave()
{
263
    if (d->m_hasShutdown)
264 265
        return;

266 267
    disconnect(d->m_adapter, SIGNAL(connectionStartupFailed()), this, SLOT(connectionStartupFailed()));
    d->m_adapter->closeConnection();
268

269
    if (d->m_addedAdapterToObjectPool) {
270
        ExtensionSystem::PluginManager *pluginManager = ExtensionSystem::PluginManager::instance();
271
        pluginManager->removeObject(d->m_adapter);
272
        pluginManager->removeObject(this);
273 274
    }

275
    if (d->m_attachToRunningExternalApp) {
276 277 278 279
        setState(EngineShutdownRequested, true);
        setState(EngineShutdownOk, true);
        setState(DebuggerFinished, true);
    } else {
280
        if (d->m_applicationLauncher.isRunning()) {
281
            // should only happen if engine is ill
282 283
            disconnect(&d->m_applicationLauncher, SIGNAL(processExited(int)), this, SLOT(disconnected()));
            d->m_applicationLauncher.stop();
284 285
        }
    }
286
    d->m_hasShutdown = true;
287 288
}

hjk's avatar
hjk committed
289
void QmlEngine::shutdownInferior()
290
{
291
    // don't do normal shutdown if running as slave engine
292
    if (d->m_attachToRunningExternalApp)
293 294
        return;

hjk's avatar
hjk committed
295
    QTC_ASSERT(state() == InferiorShutdownRequested, qDebug() << state());
296
    if (!d->m_applicationLauncher.isRunning()) {
Lasse Holmstedt's avatar
Lasse Holmstedt committed
297 298
        showMessage(tr("Trying to stop while process is no longer running."), LogError);
    } else {
299 300 301
        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
302
    }
hjk's avatar
hjk committed
303 304 305 306 307 308
    notifyInferiorShutdownOk();
}

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

310
    shutdownEngineAsSlave();
Lasse Holmstedt's avatar
Lasse Holmstedt committed
311

312
    notifyEngineShutdownOk();
313
    plugin()->showMessage(QString(), StatusBar);
314 315
}

hjk's avatar
hjk committed
316
void QmlEngine::setupEngine()
317
{
318 319 320
    d->m_adapter->setMaxConnectionAttempts(MaxConnectionAttempts);
    d->m_adapter->setConnectionAttemptInterval(ConnectionAttemptDefaultInterval);
    connect(d->m_adapter, SIGNAL(connectionError(QAbstractSocket::SocketError)),
321
            SLOT(connectionError(QAbstractSocket::SocketError)));
322 323
    connect(d->m_adapter, SIGNAL(connected()), SLOT(connectionEstablished()));
    connect(d->m_adapter, SIGNAL(connectionStartupFailed()), SLOT(connectionStartupFailed()));
Lasse Holmstedt's avatar
Lasse Holmstedt committed
324

325
    notifyEngineSetupOk();
326 327
}

328 329
void QmlEngine::continueInferior()
{
hjk's avatar
hjk committed
330
    QTC_ASSERT(state() == InferiorStopOk, qDebug() << state());
331 332 333 334
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
    rs << QByteArray("CONTINUE");
    sendMessage(reply);
335
    resetLocation();
hjk's avatar
hjk committed
336 337
    notifyInferiorRunRequested();
    notifyInferiorRunOk();
338 339 340 341
}

void QmlEngine::interruptInferior()
{
342 343 344 345
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
    rs << QByteArray("INTERRUPT");
    sendMessage(reply);
346
    notifyInferiorStopOk();
347 348 349 350
}

void QmlEngine::executeStep()
{
351 352 353 354
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
    rs << QByteArray("STEPINTO");
    sendMessage(reply);
hjk's avatar
hjk committed
355 356
    notifyInferiorRunRequested();
    notifyInferiorRunOk();
357 358 359 360
}

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

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

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

void QmlEngine::executeNextI()
{
391
    SDEBUG("QmlEngine::executeNextI()");
392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416
}

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)
417 418 419 420 421 422 423

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

424
    gotoLocation(stackHandler()->frames().value(index), true);
425 426 427 428 429 430 431 432 433
}

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

void QmlEngine::attemptBreakpointSynchronization()
{
434
    Internal::BreakHandler *handler = breakHandler();
435 436 437
    //bool updateNeeded = false;
    QSet< QPair<QString, qint32> > breakList;
    for (int index = 0; index != handler->size(); ++index) {
438
        Internal::BreakpointData *data = handler->at(index);
439 440 441
        QString processedFilename = data->fileName;
        if (isShadowBuildProject())
            processedFilename = toShadowBuildFilename(data->fileName);
442
        breakList << qMakePair(processedFilename, data->lineNumber);
443 444 445 446 447 448 449 450 451 452
    }

    {
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
    rs << QByteArray("BREAKPOINTS");
    rs << breakList;
    //qDebug() << Q_FUNC_INFO << breakList;
    sendMessage(reply);
    }
453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480
}

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)
{
481 482
    // this is processed by QML inspector, which has deps to qml js editor. Makes life easier.
    emit tooltipRequested(mousePos, editor, cursorPos);
483 484 485 486 487 488 489 490
}

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

491 492
void QmlEngine::assignValueInDebugger(const Internal::WatchData *,
                                      const QString &expression, const QVariant &valueV)
493
{
494 495 496 497 498 499 500 501 502
    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");
503
            rs << expression.toUtf8() << objectId << property << valueV.toString();
504 505 506
            sendMessage(reply);
        }
    }
507 508
}

509
void QmlEngine::updateWatchData(const Internal::WatchData &data, const Internal::WatchUpdateFlags &)
510
{
Olivier Goffart's avatar
Olivier Goffart committed
511
//    qDebug() << "UPDATE WATCH DATA" << data.toString();
512
    //watchHandler()->rebuildModel();
513
    showStatusMessage(tr("Stopped."), 5000);
514

515
    if (!data.name.isEmpty() && data.isValueNeeded()) {
516 517 518 519 520 521 522
        QByteArray reply;
        QDataStream rs(&reply, QIODevice::WriteOnly);
        rs << QByteArray("EXEC");
        rs << data.iname << data.name;
        sendMessage(reply);
    }

523 524
    if (!data.name.isEmpty() && data.isChildrenNeeded()
            && watchHandler()->isExpandedIName(data.iname))
525 526
        expandObject(data.iname, data.objectId);

527 528 529 530 531 532 533
    {
        QByteArray reply;
        QDataStream rs(&reply, QIODevice::WriteOnly);
        rs << QByteArray("WATCH_EXPRESSIONS");
        rs << watchHandler()->watchedExpressions();
        sendMessage(reply);
    }
534 535 536

    if (!data.isSomethingNeeded())
        watchHandler()->insertData(data);
537 538
}

539 540 541 542 543 544 545 546 547
void QmlEngine::expandObject(const QByteArray& iname, quint64 objectId)
{
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
    rs << QByteArray("EXPAND");
    rs << iname << objectId;
    sendMessage(reply);
}

548 549
void QmlEngine::sendPing()
{
550
    d->m_ping++;
551 552 553
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
    rs << QByteArray("PING");
554
    rs << d->m_ping;
555 556 557
    sendMessage(reply);
}

558
namespace Internal {
559 560 561 562
DebuggerEngine *createQmlEngine(const DebuggerStartParameters &sp)
{
    return new QmlEngine(sp);
}
563
} // namespace Internal
564 565 566 567 568 569 570 571 572 573 574 575 576

unsigned QmlEngine::debuggerCapabilities() const
{
    return AddWatcherCapability;
    /*ReverseSteppingCapability | SnapshotCapability
        | AutoDerefPointersCapability | DisassemblerCapability
        | RegisterCapability | ShowMemoryCapability
        | JumpToLineCapability | ReloadModuleCapability
        | ReloadModuleSymbolsCapability | BreakOnThrowAndCatchCapability
        | ReturnFromFunctionCapability
        | CreateFullBacktraceCapability
        | WatchpointCapability
        | AddWatcherCapability;*/
577 578 579 580 581 582 583 584 585 586
}

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

    QByteArray command;
    stream >> command;

587
    showMessage(QLatin1String("RECEIVED RESPONSE: ") + Internal::quoteUnprintableLatin1(message));
588
    if (command == "STOPPED") {
589 590 591
        if (state() == InferiorRunOk) {
            notifyInferiorSpontaneousStop();
        }
592

593
        QList<QPair<QString, QPair<QString, qint32> > > backtrace;
594 595
        QList<Internal::WatchData> watches;
        QList<Internal::WatchData> locals;
596
        stream >> backtrace >> watches >> locals;
597

598
        Internal::StackFrames stackFrames;
599 600
        typedef QPair<QString, QPair<QString, qint32> > Iterator;
        foreach (const Iterator &it, backtrace) {
601
            Internal::StackFrame frame;
602 603 604
            frame.file = it.second.first;
            frame.line = it.second.second;
            frame.function = it.first;
605
            stackFrames.append(frame);
606 607 608 609 610 611
        }

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

        watchHandler()->beginCycle();
612
        bool needPing = false;
613

614
        foreach (Internal::WatchData data, watches) {
615
            data.iname = watchHandler()->watcherName(data.exp);
616
            watchHandler()->insertData(data);
617

618 619
            if (watchHandler()->expandedINames().contains(data.iname)) {
                needPing = true;
620
                expandObject(data.iname, data.objectId);
621
            }
622 623
        }

624
        foreach (Internal::WatchData data, locals) {
625 626
            data.iname = "local." + data.exp;
            watchHandler()->insertData(data);
627

628 629
            if (watchHandler()->expandedINames().contains(data.iname)) {
                needPing = true;
630
                expandObject(data.iname, data.objectId);
631
            }
hjk's avatar
hjk committed
632 633
        }

634 635 636 637
        if (needPing)
            sendPing();
        else
            watchHandler()->endCycle();
638

639 640 641
        bool becauseOfException;
        stream >> becauseOfException;
        if (becauseOfException) {
642 643 644
            QString error;
            stream >> error;

645 646 647
            QString msg =
                tr("<p>An Uncaught Exception occured in <i>%1</i>:</p><p>%2</p>")
                    .arg(stackFrames.value(0).file, Qt::escape(error));
648 649 650
            showMessageBox(QMessageBox::Information, tr("Uncaught Exception"), msg);
        }

651

652
    } else if (command == "RESULT") {
653
        Internal::WatchData data;
654 655 656
        QByteArray iname;
        stream >> iname >> data;
        data.iname = iname;
657 658 659 660 661 662 663
        if (iname.startsWith("watch.")) {
            watchHandler()->insertData(data);
        } else if(iname == "console") {
            plugin()->showMessage(data.value, ScriptConsoleOutput);
        } else {
            qWarning() << "QmlEngine: Unexcpected result: " << iname << data.value;
        }
664
    } else if (command == "EXPANDED") {
665
        QList<Internal::WatchData> result;
666 667
        QByteArray iname;
        stream >> iname >> result;
668
        bool needPing = false;
669
        foreach (Internal::WatchData data, result) {
670 671 672
            data.iname = iname + '.' + data.exp;
            watchHandler()->insertData(data);

673 674
            if (watchHandler()->expandedINames().contains(data.iname)) {
                needPing = true;
675
                expandObject(data.iname, data.objectId);
676
            }
677
        }
678 679
        if (needPing)
            sendPing();
680
    } else if (command == "LOCALS") {
681
        QList<Internal::WatchData> locals;
682 683 684
        int frameId;
        stream >> frameId >> locals;
        watchHandler()->beginCycle();
685
        bool needPing = false;
686
        foreach (Internal::WatchData data, locals) {
687 688
            data.iname = "local." + data.exp;
            watchHandler()->insertData(data);
689 690
            if (watchHandler()->expandedINames().contains(data.iname)) {
                needPing = true;
691
                expandObject(data.iname, data.objectId);
692
            }
693
        }
694 695 696 697 698 699 700 701
        if (needPing)
            sendPing();
        else
            watchHandler()->endCycle();

    } else if (command == "PONG") {
        int ping;
        stream >> ping;
702
        if (ping == d->m_ping)
703
            watchHandler()->endCycle();
704 705 706 707 708 709
    } else {
        qDebug() << Q_FUNC_INFO << "Unknown command: " << command;
    }

}

710 711
void QmlEngine::disconnected()
{
Lasse Holmstedt's avatar
Lasse Holmstedt committed
712
    plugin()->showMessage(tr("QML Debugger disconnected."), StatusBar);
713 714 715
    notifyInferiorExited();
}

716 717 718 719 720 721 722 723 724
void QmlEngine::executeDebuggerCommand(const QString& command)
{
    QByteArray reply;
    QDataStream rs(&reply, QIODevice::WriteOnly);
    rs << QByteArray("EXEC");
    rs << QByteArray("console") << command;
    sendMessage(reply);
}

725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 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 778 779 780 781 782 783 784 785 786 787 788 789 790 791
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;
}
792

793
} // namespace Debugger
794