OSDN Git Service

Merge remote branch 'origin/2.1'
[qt-creator-jp/qt-creator-jp.git] / src / plugins / debugger / qml / qmlengine.cpp
index b1ebfb1..9e1599a 100644 (file)
 #include "qmlengine.h"
 #include "qmladapter.h"
 
+#include "debuggeractions.h"
+#include "debuggertooltip.h"
 #include "debuggerconstants.h"
 #include "debuggerplugin.h"
 #include "debuggerdialogs.h"
 #include "debuggerstringutils.h"
 #include "debuggeruiswitcher.h"
+#include "debuggerrunner.h"
 
 #include "breakhandler.h"
 #include "moduleshandler.h"
@@ -44,8 +47,9 @@
 #include "watchutils.h"
 
 #include <extensionsystem/pluginmanager.h>
-#include <projectexplorer/environment.h>
+#include <projectexplorer/applicationlauncher.h>
 
+#include <utils/environment.h>
 #include <utils/qtcassert.h>
 
 #include <QtCore/QDateTime>
@@ -87,13 +91,35 @@ QDataStream& operator>>(QDataStream& s, WatchData &data)
     QString type;
     bool hasChildren;
     s >> data.exp >> data.name >> value >> type >> hasChildren >> data.objectId;
-    data.setType(type, false);
+    data.setType(type.toUtf8(), false);
     data.setValue(value);
     data.setHasChildren(hasChildren);
     data.setAllUnneeded();
     return s;
 }
 
+} // 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)
+{
+}
+
 ///////////////////////////////////////////////////////////////////////
 //
 // QmlEngine
@@ -101,27 +127,65 @@ QDataStream& operator>>(QDataStream& s, WatchData &data)
 ///////////////////////////////////////////////////////////////////////
 
 QmlEngine::QmlEngine(const DebuggerStartParameters &startParameters)
-    : DebuggerEngine(startParameters)
-    , m_ping(0)
-    , m_adapter(new QmlAdapter(this))
-    , m_addedAdapterToObjectPool(false)
+    : DebuggerEngine(startParameters), d(new QmlEnginePrivate(this))
 {
-
+    setObjectName(QLatin1String("QmlEngine"));
 }
 
 QmlEngine::~QmlEngine()
 {
 }
 
+void QmlEngine::setAttachToRunningExternalApp(bool value)
+{
+    d->m_attachToRunningExternalApp = value;
+}
+
+void QmlEngine::pauseConnection()
+{
+    d->m_adapter->pauseConnection();
+}
+
+void QmlEngine::gotoLocation(const QString &fileName, int lineNumber, bool setMarker)
+{
+    QString processedFilename = fileName;
+
+    if (isShadowBuildProject())
+        processedFilename = fromShadowBuildFilename(fileName);
+
+    DebuggerEngine::gotoLocation(processedFilename, lineNumber, setMarker);
+}
+
+void QmlEngine::gotoLocation(const Internal::StackFrame &frame, bool setMarker)
+{
+    Internal::StackFrame adjustedFrame = frame;
+    if (isShadowBuildProject())
+        adjustedFrame.file = fromShadowBuildFilename(frame.file);
+
+    DebuggerEngine::gotoLocation(adjustedFrame, setMarker);
+}
+
 void QmlEngine::setupInferior()
 {
     QTC_ASSERT(state() == InferiorSetupRequested, qDebug() << state());
 
-    connect(&m_applicationLauncher, SIGNAL(processExited(int)), SLOT(disconnected()));
-    m_applicationLauncher.setEnvironment(startParameters().environment);
-    m_applicationLauncher.setWorkingDirectory(startParameters().workingDirectory);
-
-    notifyInferiorSetupOk();
+    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();
+    }
 }
 
 void QmlEngine::connectionEstablished()
@@ -129,8 +193,9 @@ void QmlEngine::connectionEstablished()
     attemptBreakpointSynchronization();
 
     ExtensionSystem::PluginManager *pluginManager = ExtensionSystem::PluginManager::instance();
-    pluginManager->addObject(m_adapter);
-    m_addedAdapterToObjectPool = true;
+    pluginManager->addObject(d->m_adapter);
+    pluginManager->addObject(this);
+    d->m_addedAdapterToObjectPool = true;
 
     plugin()->showMessage(tr("QML Debugger connected."), StatusBar);
 
@@ -141,36 +206,106 @@ void QmlEngine::connectionStartupFailed()
 {
     QMessageBox::critical(0,
                           tr("Failed to connect to debugger"),
-                          tr("Could not connect to debugger server.") );
+                          tr("Could not connect to QML debugger server at %1:%2.")
+                          .arg(startParameters().qmlServerAddress)
+                          .arg(startParameters().qmlServerPort));
     notifyEngineRunFailed();
 }
 
-void QmlEngine::connectionError()
+void QmlEngine::connectionError(QAbstractSocket::SocketError socketError)
+{
+    if (socketError ==QAbstractSocket::RemoteHostClosedError)
+        plugin()->showMessage(tr("QML Debugger: Remote host closed connection."), StatusBar);
+}
+
+
+void QmlEngine::serviceConnectionError(const QString &serviceName)
 {
-    // do nothing for now - only exit the debugger when inferior exits.
+    plugin()->showMessage(tr("QML Debugger: Couldn't connect to service '%1'.").arg(serviceName), StatusBar);
 }
 
 void QmlEngine::runEngine()
 {
     QTC_ASSERT(state() == EngineRunRequested, qDebug() << state());
 
-    // ### TODO for non-qmlproject apps, start in a different way
-    m_applicationLauncher.start(ProjectExplorer::ApplicationLauncher::Gui,
-                                startParameters().executable,
-                                startParameters().processArgs);
+    if (!d->m_attachToRunningExternalApp) {
+        d->m_applicationLauncher.start(ProjectExplorer::ApplicationLauncher::Gui,
+                                    startParameters().executable,
+                                    startParameters().processArgs);
+    }
 
-    m_adapter->beginConnection();
+    d->m_adapter->beginConnection();
     plugin()->showMessage(tr("QML Debugger connecting..."), StatusBar);
 }
 
+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();
+}
+
+void QmlEngine::shutdownInferiorAsSlave()
+{
+    resetLocation();
+
+    // 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()
+{
+    if (d->m_hasShutdown)
+        return;
+
+    disconnect(d->m_adapter, SIGNAL(connectionStartupFailed()), this, SLOT(connectionStartupFailed()));
+    d->m_adapter->closeConnection();
+
+    if (d->m_addedAdapterToObjectPool) {
+        ExtensionSystem::PluginManager *pluginManager = ExtensionSystem::PluginManager::instance();
+        pluginManager->removeObject(d->m_adapter);
+        pluginManager->removeObject(this);
+    }
+
+    if (d->m_attachToRunningExternalApp) {
+        setState(EngineShutdownRequested, true);
+        setState(EngineShutdownOk, true);
+        setState(DebuggerFinished, true);
+    } else {
+        if (d->m_applicationLauncher.isRunning()) {
+            // should only happen if engine is ill
+            disconnect(&d->m_applicationLauncher, SIGNAL(processExited(int)), this, SLOT(disconnected()));
+            d->m_applicationLauncher.stop();
+        }
+    }
+    d->m_hasShutdown = true;
+}
+
 void QmlEngine::shutdownInferior()
 {
+    // don't do normal shutdown if running as slave engine
+    if (d->m_attachToRunningExternalApp)
+        return;
+
     QTC_ASSERT(state() == InferiorShutdownRequested, qDebug() << state());
-    if (!m_applicationLauncher.isRunning()) {
+    if (!d->m_applicationLauncher.isRunning()) {
         showMessage(tr("Trying to stop while process is no longer running."), LogError);
     } else {
-        disconnect(&m_applicationLauncher, SIGNAL(processExited(int)), this, SLOT(disconnected()));
-        m_applicationLauncher.stop();
+        disconnect(&d->m_applicationLauncher, SIGNAL(processExited(int)), this, SLOT(disconnected()));
+        if (!d->m_attachToRunningExternalApp)
+            d->m_applicationLauncher.stop();
     }
     notifyInferiorShutdownOk();
 }
@@ -179,27 +314,28 @@ void QmlEngine::shutdownEngine()
 {
     QTC_ASSERT(state() == EngineShutdownRequested, qDebug() << state());
 
-    if (m_addedAdapterToObjectPool) {
-        ExtensionSystem::PluginManager *pluginManager = ExtensionSystem::PluginManager::instance();
-        pluginManager->removeObject(m_adapter);
-    }
-
-    if (m_applicationLauncher.isRunning()) {
-        // should only happen if engine is ill
-        disconnect(&m_applicationLauncher, SIGNAL(processExited(int)), this, SLOT(disconnected()));
-        m_applicationLauncher.stop();
-    }
+    shutdownEngineAsSlave();
 
     notifyEngineShutdownOk();
+    plugin()->showMessage(QString(), StatusBar);
 }
 
 void QmlEngine::setupEngine()
 {
-    m_adapter->setMaxConnectionAttempts(MaxConnectionAttempts);
-    m_adapter->setConnectionAttemptInterval(ConnectionAttemptDefaultInterval);
-    connect(m_adapter, SIGNAL(connectionError()), SLOT(connectionError()));
-    connect(m_adapter, SIGNAL(connected()), SLOT(connectionEstablished()));
-    connect(m_adapter, SIGNAL(connectionStartupFailed()), SLOT(connectionStartupFailed()));
+    if (!d->m_attachToRunningExternalApp
+     && !startParameters().qmlObserverAvailable
+     && Internal::theDebuggerBoolSetting(Internal::UseQmlObserver))
+    {
+        showQmlObserverToolWarning();
+    }
+
+    d->m_adapter->setMaxConnectionAttempts(MaxConnectionAttempts);
+    d->m_adapter->setConnectionAttemptInterval(ConnectionAttemptDefaultInterval);
+    connect(d->m_adapter, SIGNAL(connectionError(QAbstractSocket::SocketError)),
+            SLOT(connectionError(QAbstractSocket::SocketError)));
+    connect(d->m_adapter, SIGNAL(serviceConnectionError(QString)), SLOT(serviceConnectionError(QString)));
+    connect(d->m_adapter, SIGNAL(connected()), SLOT(connectionEstablished()));
+    connect(d->m_adapter, SIGNAL(connectionStartupFailed()), SLOT(connectionStartupFailed()));
 
     notifyEngineSetupOk();
 }
@@ -310,12 +446,15 @@ void QmlEngine::selectThread(int index)
 
 void QmlEngine::attemptBreakpointSynchronization()
 {
-    BreakHandler *handler = breakHandler();
+    Internal::BreakHandler *handler = breakHandler();
     //bool updateNeeded = false;
     QSet< QPair<QString, qint32> > breakList;
     for (int index = 0; index != handler->size(); ++index) {
-        BreakpointData *data = handler->at(index);
-        breakList << qMakePair(data->fileName, data->lineNumber.toInt());
+        Internal::BreakpointData *data = handler->at(index);
+        QString processedFilename = data->fileName;
+        if (isShadowBuildProject())
+            processedFilename = toShadowBuildFilename(data->fileName);
+        breakList << qMakePair(processedFilename, data->lineNumber);
     }
 
     {
@@ -328,6 +467,11 @@ void QmlEngine::attemptBreakpointSynchronization()
     }
 }
 
+bool QmlEngine::acceptsBreakpoint(const Internal::BreakpointData *br)
+{
+    return (br->fileName.endsWith(QLatin1String("qml")) || br->fileName.endsWith(QLatin1String("js")));
+}
+
 void QmlEngine::loadSymbols(const QString &moduleName)
 {
     Q_UNUSED(moduleName)
@@ -352,15 +496,10 @@ void QmlEngine::requestModuleSymbols(const QString &moduleName)
 //
 //////////////////////////////////////////////////////////////////////
 
-static WatchData m_toolTip;
-static QPoint m_toolTipPos;
-static QHash<QString, WatchData> m_toolTipCache;
-
 void QmlEngine::setToolTipExpression(const QPoint &mousePos, TextEditor::ITextEditor *editor, int cursorPos)
 {
-    Q_UNUSED(mousePos)
-    Q_UNUSED(editor)
-    Q_UNUSED(cursorPos)
+    // this is processed by QML inspector, which has deps to qml js editor. Makes life easier.
+    emit tooltipRequested(mousePos, editor, cursorPos);
 }
 
 //////////////////////////////////////////////////////////////////////
@@ -369,8 +508,8 @@ void QmlEngine::setToolTipExpression(const QPoint &mousePos, TextEditor::ITextEd
 //
 //////////////////////////////////////////////////////////////////////
 
-void QmlEngine::assignValueInDebugger(const QString &expression,
-    const QString &value)
+void QmlEngine::assignValueInDebugger(const Internal::WatchData *,
+                                      const QString &expression, const QVariant &valueV)
 {
     QRegExp inObject("@([0-9a-fA-F]+)->(.+)");
     if (inObject.exactMatch(expression)) {
@@ -381,13 +520,13 @@ void QmlEngine::assignValueInDebugger(const QString &expression,
             QByteArray reply;
             QDataStream rs(&reply, QIODevice::WriteOnly);
             rs << QByteArray("SET_PROPERTY");
-            rs << expression.toUtf8() << objectId << property << value;
+            rs << expression.toUtf8() << objectId << property << valueV.toString();
             sendMessage(reply);
         }
     }
 }
 
-void QmlEngine::updateWatchData(const WatchData &data)
+void QmlEngine::updateWatchData(const Internal::WatchData &data, const Internal::WatchUpdateFlags &)
 {
 //    qDebug() << "UPDATE WATCH DATA" << data.toString();
     //watchHandler()->rebuildModel();
@@ -428,18 +567,20 @@ void QmlEngine::expandObject(const QByteArray& iname, quint64 objectId)
 
 void QmlEngine::sendPing()
 {
-    m_ping++;
+    d->m_ping++;
     QByteArray reply;
     QDataStream rs(&reply, QIODevice::WriteOnly);
     rs << QByteArray("PING");
-    rs << m_ping;
+    rs << d->m_ping;
     sendMessage(reply);
 }
 
+namespace Internal {
 DebuggerEngine *createQmlEngine(const DebuggerStartParameters &sp)
 {
     return new QmlEngine(sp);
 }
+} // namespace Internal
 
 unsigned QmlEngine::debuggerCapabilities() const
 {
@@ -463,19 +604,21 @@ void QmlEngine::messageReceived(const QByteArray &message)
     QByteArray command;
     stream >> command;
 
-    showMessage(_("RECEIVED RESPONSE: ") + quoteUnprintableLatin1(message));
+    showMessage(QLatin1String("RECEIVED RESPONSE: ") + Internal::quoteUnprintableLatin1(message));
     if (command == "STOPPED") {
-        notifyInferiorSpontaneousStop();
+        if (state() == InferiorRunOk) {
+            notifyInferiorSpontaneousStop();
+        }
 
         QList<QPair<QString, QPair<QString, qint32> > > backtrace;
-        QList<WatchData> watches;
-        QList<WatchData> locals;
+        QList<Internal::WatchData> watches;
+        QList<Internal::WatchData> locals;
         stream >> backtrace >> watches >> locals;
 
-        StackFrames stackFrames;
+        Internal::StackFrames stackFrames;
         typedef QPair<QString, QPair<QString, qint32> > Iterator;
         foreach (const Iterator &it, backtrace) {
-            StackFrame frame;
+            Internal::StackFrame frame;
             frame.file = it.second.first;
             frame.line = it.second.second;
             frame.function = it.first;
@@ -488,7 +631,7 @@ void QmlEngine::messageReceived(const QByteArray &message)
         watchHandler()->beginCycle();
         bool needPing = false;
 
-        foreach (WatchData data, watches) {
+        foreach (Internal::WatchData data, watches) {
             data.iname = watchHandler()->watcherName(data.exp);
             watchHandler()->insertData(data);
 
@@ -498,7 +641,7 @@ void QmlEngine::messageReceived(const QByteArray &message)
             }
         }
 
-        foreach (WatchData data, locals) {
+        foreach (Internal::WatchData data, locals) {
             data.iname = "local." + data.exp;
             watchHandler()->insertData(data);
 
@@ -513,14 +656,9 @@ void QmlEngine::messageReceived(const QByteArray &message)
         else
             watchHandler()->endCycle();
 
-        // Ensure we got the right ui right now.
-        Debugger::DebuggerUISwitcher *uiSwitcher
-            = Debugger::DebuggerUISwitcher::instance();
-        uiSwitcher->setActiveLanguage("C++");
-
-        bool becauseOfexception;
-        stream >> becauseOfexception;
-        if (becauseOfexception) {
+        bool becauseOfException;
+        stream >> becauseOfException;
+        if (becauseOfException) {
             QString error;
             stream >> error;
 
@@ -528,22 +666,52 @@ void QmlEngine::messageReceived(const QByteArray &message)
                 tr("<p>An Uncaught Exception occured in <i>%1</i>:</p><p>%2</p>")
                     .arg(stackFrames.value(0).file, Qt::escape(error));
             showMessageBox(QMessageBox::Information, tr("Uncaught Exception"), msg);
-        }
+        } 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);
+                }
+            }
 
+            Internal::BreakHandler *handler = breakHandler();
+            for (int index = 0; index != handler->size(); ++index) {
+                Internal::BreakpointData *data = handler->at(index);
+                QString processedFilename = data->fileName;
 
+                if (processedFilename == file
+                        && data->lineNumber == line) {
+                    data->pending = false;
+                    data->updateMarker();
+                }
+            }
+        }
     } else if (command == "RESULT") {
-        WatchData data;
+        Internal::WatchData data;
         QByteArray iname;
         stream >> iname >> data;
         data.iname = iname;
-        watchHandler()->insertData(data);
-
+        if (iname.startsWith("watch.")) {
+            watchHandler()->insertData(data);
+        } else if(iname == "console") {
+            plugin()->showMessage(data.value, ScriptConsoleOutput);
+        } else {
+            qWarning() << "QmlEngine: Unexcpected result: " << iname << data.value;
+        }
     } else if (command == "EXPANDED") {
-        QList<WatchData> result;
+        QList<Internal::WatchData> result;
         QByteArray iname;
         stream >> iname >> result;
         bool needPing = false;
-        foreach (WatchData data, result) {
+        foreach (Internal::WatchData data, result) {
             data.iname = iname + '.' + data.exp;
             watchHandler()->insertData(data);
 
@@ -555,12 +723,12 @@ void QmlEngine::messageReceived(const QByteArray &message)
         if (needPing)
             sendPing();
     } else if (command == "LOCALS") {
-        QList<WatchData> locals;
+        QList<Internal::WatchData> locals;
         int frameId;
         stream >> frameId >> locals;
         watchHandler()->beginCycle();
         bool needPing = false;
-        foreach (WatchData data, locals) {
+        foreach (Internal::WatchData data, locals) {
             data.iname = "local." + data.exp;
             watchHandler()->insertData(data);
             if (watchHandler()->expandedINames().contains(data.iname)) {
@@ -576,7 +744,7 @@ void QmlEngine::messageReceived(const QByteArray &message)
     } else if (command == "PONG") {
         int ping;
         stream >> ping;
-        if (ping == m_ping)
+        if (ping == d->m_ping)
             watchHandler()->endCycle();
     } else {
         qDebug() << Q_FUNC_INFO << "Unknown command: " << command;
@@ -590,7 +758,82 @@ void QmlEngine::disconnected()
     notifyInferiorExited();
 }
 
+void QmlEngine::executeDebuggerCommand(const QString& command)
+{
+    QByteArray reply;
+    QDataStream rs(&reply, QIODevice::WriteOnly);
+    rs << QByteArray("EXEC");
+    rs << QByteArray("console") << command;
+    sendMessage(reply);
+}
+
+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;
+}
 
-} // namespace Internal
 } // namespace Debugger