#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"
#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>
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
///////////////////////////////////////////////////////////////////////
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()
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);
{
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();
}
{
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();
}
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);
}
{
}
}
+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)
//
//////////////////////////////////////////////////////////////////////
-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);
}
//////////////////////////////////////////////////////////////////////
//
//////////////////////////////////////////////////////////////////////
-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)) {
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();
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
{
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;
watchHandler()->beginCycle();
bool needPing = false;
- foreach (WatchData data, watches) {
+ foreach (Internal::WatchData data, watches) {
data.iname = watchHandler()->watcherName(data.exp);
watchHandler()->insertData(data);
}
}
- foreach (WatchData data, locals) {
+ foreach (Internal::WatchData data, locals) {
data.iname = "local." + data.exp;
watchHandler()->insertData(data);
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;
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);
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)) {
} 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;
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