qmlengine.cpp 25.2 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
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
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;
}
843

844
} // namespace Debugger
845