Commit 990ec1be authored by Lasse Holmstedt's avatar Lasse Holmstedt

Added debugging mode for simultaneous QML and C++ debugging

It's not yet possible to attach to an external app running a qml
debugging server, because the server is only started on startup if an
env variable is set. Changing this requires action from Brisbane, but
even the current solution works for C++ apps with QML in them.

Task-number: BAUHAUS-585
Reviewed-by: dt
parent 6c244f21
......@@ -1771,6 +1771,8 @@ void DebuggerManager::setState(DebuggerState state, bool forced)
d->m_state = state;
emit stateChanged(state);
//if (d->m_state == InferiorStopped)
// resetLocation();
......
......@@ -169,6 +169,11 @@ DebuggerRunControl::DebuggerRunControl(DebuggerManager *manager, const DebuggerS
m_startParameters->useTerminal = false;
}
void DebuggerRunControl::setCustomEnvironment(ProjectExplorer::Environment env)
{
m_startParameters->environment = env.toStringList();
}
void DebuggerRunControl::init()
{
connect(m_manager, SIGNAL(debuggingFinished()),
......
......@@ -31,14 +31,14 @@
#define DEBUGGERRUNNER_H
#include "debuggermanager.h"
#include "debugger_global.h"
#include <projectexplorer/runconfiguration.h>
#include <projectexplorer/applicationrunconfiguration.h>
namespace Debugger {
namespace Internal {
class DebuggerRunControlFactory
class DEBUGGER_EXPORT DebuggerRunControlFactory
: public ProjectExplorer::IRunControlFactory
{
Q_OBJECT
......@@ -63,7 +63,7 @@ private:
};
// This is a job description
class DebuggerRunControl
class DEBUGGER_EXPORT DebuggerRunControl
: public ProjectExplorer::RunControl
{
Q_OBJECT
......@@ -73,6 +73,7 @@ public:
ProjectExplorer::LocalApplicationRunConfiguration *runConfiguration);
DebuggerRunControl(DebuggerManager *manager, const DebuggerStartParametersPtr &startParameters);
void setCustomEnvironment(ProjectExplorer::Environment env);
// ProjectExplorer::RunControl
virtual void start();
......
......@@ -263,9 +263,12 @@ void DebuggerUISwitcher::addLanguage(const QString &langName, const QList<int> &
d->m_languageActionGroup->addAction(langChange);
QString prefix = tr("Alt+L");
connect(langChange, SIGNAL(triggered()), SLOT(langChangeTriggered()));
Core::Command *cmd = am->registerAction(langChange,
"Debugger.Language." + langName, d->m_globalContext);
cmd->setDefaultKeySequence(QKeySequence(QString("%1,%2").arg(prefix, QString::number(d->m_languages.count()))));
d->m_languageMenu->addAction(cmd);
}
......
......@@ -178,7 +178,7 @@ bool WatchTableModel::setData ( const QModelIndex & index, const QVariant & valu
Qt::ItemFlags WatchTableModel::flags ( const QModelIndex & index ) const
{
if (index.column() == C_VALUE)
return Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled;
return Qt::ItemIsSelectable | Qt::ItemIsEnabled; // Qt::ItemIsEditable | <- disabled for now
return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
}
......
#include "inspectorsettings.h"
#include "qmlinspectorconstants.h"
#include <QtCore/QSettings>
namespace Qml {
namespace Internal {
InspectorSettings::InspectorSettings() : m_externalPort(3768), m_externalUrl("127.0.0.1")
{
}
void InspectorSettings::readSettings(QSettings *settings)
{
settings->beginGroup(QLatin1String(Qml::Constants::S_QML_INSPECTOR));
m_externalPort= settings->value(QLatin1String(Qml::Constants::S_EXTERNALPORT_KEY), 3768).toUInt();
m_externalUrl = settings->value(QLatin1String(Qml::Constants::S_EXTERNALURL_KEY), "127.0.0.1").toString();
settings->endGroup();
}
void InspectorSettings::saveSettings(QSettings *settings) const
{
settings->beginGroup(QLatin1String(Qml::Constants::S_QML_INSPECTOR));
settings->setValue(QLatin1String(Qml::Constants::S_EXTERNALPORT_KEY), m_externalPort);
settings->setValue(QLatin1String(Qml::Constants::S_EXTERNALURL_KEY), m_externalUrl);
settings->endGroup();
}
quint16 InspectorSettings::externalPort() const
{
return m_externalPort;
}
QString InspectorSettings::externalUrl() const
{
return m_externalUrl;
}
void InspectorSettings::setExternalPort(quint16 port)
{
m_externalPort = port;
}
void InspectorSettings::setExternalUrl(const QString &url)
{
m_externalUrl = url;
}
} // Internal
} // Qml
#ifndef INSPECTORSETTINGS_H
#define INSPECTORSETTINGS_H
#include <QString>
#include "inspectorsettings.h"
QT_FORWARD_DECLARE_CLASS(QSettings)
namespace Qml {
namespace Internal {
class InspectorSettings
{
public:
InspectorSettings();
void readSettings(QSettings *settings);
void saveSettings(QSettings *settings) const;
void setExternalPort(quint16 port);
void setExternalUrl(const QString &url);
quint16 externalPort() const;
QString externalUrl() const;
private:
quint16 m_externalPort;
QString m_externalUrl;
};
} // Internal
} // Qml
#endif // INSPECTORSETTINGS_H
......@@ -31,15 +31,17 @@
#include "qmlinspector.h"
#include "inspectoroutputwidget.h"
#include "inspectorcontext.h"
#include "startexternalqmldialog.h"
#include "components/objecttree.h"
#include "components/watchtable.h"
#include "components/canvasframerate.h"
#include "components/expressionquerywidget.h"
#include "components/objectpropertiesview.h"
#include <debugger/debuggerrunner.h>
#include <debugger/debuggermainwindow.h>
#include <debugger/debuggeruiswitcher.h>
#include <debugger/debuggerconstants.h>
#include <utils/styledbar.h>
#include <utils/fancymainwindow.h>
......@@ -54,7 +56,9 @@
#include <coreplugin/icore.h>
#include <coreplugin/coreconstants.h>
#include <coreplugin/uniqueidmanager.h>
#include <coreplugin/actionmanager/actioncontainer.h>
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/actionmanager/command.h>
#include <coreplugin/editormanager/editormanager.h>
#include <texteditor/itexteditor.h>
......@@ -64,9 +68,12 @@
#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/project.h>
#include <projectexplorer/target.h>
#include <projectexplorer/applicationrunconfiguration.h>
#include <qmlprojectmanager/qmlprojectconstants.h>
#include <qmlprojectmanager/qmlprojectrunconfiguration.h>
#include <extensionsystem/pluginmanager.h>
#include <QtCore/QDebug>
#include <QtCore/QStringList>
#include <QtCore/QTimer>
......@@ -81,6 +88,7 @@
#include <QtGui/QLineEdit>
#include <QtGui/QLabel>
#include <QtGui/QSpinBox>
#include <QtGui/QMessageBox>
#include <QtNetwork/QHostAddress>
......@@ -168,7 +176,9 @@ QmlInspector::QmlInspector(QObject *parent)
m_objectTreeDock(0),
m_frameRateDock(0),
m_propertyWatcherDock(0),
m_inspectorOutputDock(0)
m_inspectorOutputDock(0),
m_connectionTimer(new QTimer(this)),
m_connectionAttempts(0)
{
m_watchTableModel = new Internal::WatchTableModel(0, this);
......@@ -178,38 +188,72 @@ QmlInspector::QmlInspector(QObject *parent)
m_frameRateWidget = new Internal::CanvasFrameRate;
m_frameRateWidget->setObjectName(QLatin1String("QmlDebugFrameRate"));
m_expressionWidget = new Internal::ExpressionQueryWidget(Internal::ExpressionQueryWidget::SeparateEntryMode);
connect(m_connectionTimer, SIGNAL(timeout()), SLOT(pollInspector()));
}
bool QmlInspector::connectToViewer()
QmlInspector::~QmlInspector()
{
if (m_conn && m_conn->state() != QAbstractSocket::UnconnectedState)
return false;
delete m_client; m_client = 0;
m_settings.saveSettings(Core::ICore::instance()->settings());
}
if (m_conn) {
m_conn->disconnectFromHost();
delete m_conn;
m_conn = 0;
void QmlInspector::pollInspector()
{
++m_connectionAttempts;
if (connectToViewer()) {
m_connectionTimer->stop();
m_connectionAttempts = 0;
} else if (m_connectionAttempts == MaxConnectionAttempts) {
m_connectionTimer->stop();
m_connectionAttempts = 0;
QMessageBox::critical(0,
tr("Failed to connect to debugger"),
tr("Could not connect to debugger server.") );
}
}
ProjectExplorer::Project *project = ProjectExplorer::ProjectExplorerPlugin::instance()->startupProject();
if (!project) {
emit statusMessage(tr("No active project, debugging canceled."));
bool QmlInspector::setDebugConfigurationDataFromProject(ProjectExplorer::Project *projectToDebug)
{
//ProjectExplorer::Project *project = ProjectExplorer::ProjectExplorerPlugin::instance()->startupProject();
if (!projectToDebug) {
emit statusMessage(tr("Invalid project, debugging canceled."));
return false;
}
// FIXME if we have c++ project with qml files in it, it would make sense to be able to start
// the qml inspector simultaneously for that project. however, now it's not possible.
QmlProjectManager::QmlProjectRunConfiguration* config =
qobject_cast<QmlProjectManager::QmlProjectRunConfiguration*>(project->activeTarget()->activeRunConfiguration());
qobject_cast<QmlProjectManager::QmlProjectRunConfiguration*>(projectToDebug->activeTarget()->activeRunConfiguration());
if (!config) {
emit statusMessage(tr("Cannot find project run configuration, debugging canceled."));
return false;
}
m_runConfigurationDebugData.serverAddress = config->debugServerAddress();
m_runConfigurationDebugData.serverPort = config->debugServerPort();
m_connectionTimer->setInterval(ConnectionAttemptDefaultInterval);
QString host = config->debugServerAddress();
quint16 port = quint16(config->debugServerPort());
return true;
}
void QmlInspector::startConnectionTimer()
{
m_connectionTimer->start();
}
bool QmlInspector::connectToViewer()
{
if (m_conn && m_conn->state() != QAbstractSocket::UnconnectedState)
return false;
delete m_client; m_client = 0;
if (m_conn) {
m_conn->disconnectFromHost();
delete m_conn;
m_conn = 0;
}
QString host = m_runConfigurationDebugData.serverAddress;
quint16 port = quint16(m_runConfigurationDebugData.serverPort);
m_conn = new QDeclarativeDebugConnection(this);
connect(m_conn, SIGNAL(stateChanged(QAbstractSocket::SocketState)),
......@@ -399,6 +443,20 @@ void QmlInspector::createDockWidgets()
core->addContextObject(m_propWatcherContext);
core->addContextObject(m_context);
QAction *attachToExternalAction = new QAction(this);
attachToExternalAction->setText(tr("Start Debugging C++ and QML Simultaneously..."));
connect(attachToExternalAction, SIGNAL(triggered()),
this, SLOT(attachToExternalQmlApplication()));
Core::ActionManager *am = core->actionManager();
Core::ActionContainer *mstart = am->actionContainer(ProjectExplorer::Constants::M_DEBUG_STARTDEBUGGING);
Core::Command *cmd = am->registerAction(attachToExternalAction, Constants::M_ATTACH_TO_EXTERNAL,
QList<int>() << m_context->context());
cmd->setAttribute(Core::Command::CA_Hide);
mstart->addAction(cmd, Core::Constants::G_DEFAULT_ONE);
m_settings.readSettings(core->settings());
connect(m_objectTreeWidget, SIGNAL(contextHelpIdChanged(QString)), m_context,
SLOT(setContextHelpId(QString)));
connect(m_watchTableView, SIGNAL(contextHelpIdChanged(QString)), m_propWatcherContext,
......@@ -409,6 +467,91 @@ void QmlInspector::createDockWidgets()
SLOT(setContextHelpId(QString)));
}
void QmlInspector::attachToExternalQmlApplication()
{
ProjectExplorer::ProjectExplorerPlugin *pex = ProjectExplorer::ProjectExplorerPlugin::instance();
ProjectExplorer::Project *project = pex->startupProject();
ProjectExplorer::LocalApplicationRunConfiguration* runConfig =
qobject_cast<ProjectExplorer::LocalApplicationRunConfiguration*>(project->activeTarget()->activeRunConfiguration());
QString errorMessage;
if (!project)
errorMessage = QString(tr("No project was found."));
else if (!project->activeTarget() || !project->activeTarget()->activeRunConfiguration())
errorMessage = QString(tr("No run configurations were found for the project '%1'.").arg(project->displayName()));
else if (!runConfig)
errorMessage = QString(tr("No valid run configuration was found for the project %1. "
"Only locally runnable configurations are supported.\n"
"Please check your project settings.").arg(project->displayName()));
if (errorMessage.isEmpty()) {
Internal::StartExternalQmlDialog dlg(Debugger::DebuggerUISwitcher::instance()->mainWindow());
dlg.setPort(m_settings.externalPort());
dlg.setDebuggerUrl(m_settings.externalUrl());
dlg.setProjectDisplayName(project->displayName());
if (dlg.exec() != QDialog::Accepted)
return;
m_runConfigurationDebugData.serverAddress = dlg.debuggerUrl();
m_runConfigurationDebugData.serverPort = dlg.port();
m_settings.setExternalPort(dlg.port());
m_settings.setExternalUrl(dlg.debuggerUrl());
ProjectExplorer::Environment customEnv = runConfig->environment();
customEnv.set(QmlProjectManager::Constants::E_QML_DEBUG_SERVER_PORT, QString::number(m_settings.externalPort()));
ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
const QList<Debugger::Internal::DebuggerRunControlFactory *> factories = pm->getObjects<Debugger::Internal::DebuggerRunControlFactory>();
// to make sure we have a valid, debuggable run control, find the correct factory for it
if (factories.length() && factories.first()->canRun(runConfig, ProjectExplorer::Constants::DEBUGMODE)) {
ProjectExplorer::RunControl *runControl = factories.first()->create(runConfig, ProjectExplorer::Constants::DEBUGMODE);
Debugger::Internal::DebuggerRunControl *debuggableRunControl = qobject_cast<Debugger::Internal::DebuggerRunControl *>(runControl);
// modify the env
debuggableRunControl->setCustomEnvironment(customEnv);
Debugger::DebuggerManager *debugManager = Debugger::DebuggerManager::instance();
connect(debugManager, SIGNAL(stateChanged(int)), this, SLOT(debuggerStateChanged(int)));
pex->startRunControl(debuggableRunControl, ProjectExplorer::Constants::DEBUGMODE);
} else {
errorMessage = QString(tr("A valid run control was not registered in Qt Creator for this project run configuration."));
}
}
if (!errorMessage.isEmpty())
QMessageBox::warning(Core::ICore::instance()->mainWindow(), "Failed to debug C++ and QML", errorMessage);
}
void QmlInspector::debuggerStateChanged(int newState)
{
switch(newState) {
case Debugger::AdapterStartFailed:
case Debugger::InferiorStartFailed:
disconnect(Debugger::DebuggerManager::instance(), SIGNAL(stateChanged(int)), this, SLOT(debuggerStateChanged(int)));
emit statusMessage(QString(tr("Debugging failed: could not start C++ debugger.")));
break;
default:
break;
}
if (newState == Debugger::InferiorRunning) {
disconnect(Debugger::DebuggerManager::instance(), SIGNAL(stateChanged(int)), this, SLOT(debuggerStateChanged(int)));
m_connectionTimer->setInterval(ConnectionAttemptSimultaneousInterval);
startConnectionTimer();
}
}
void QmlInspector::setSimpleDockWidgetArrangement()
{
Utils::FancyMainWindow *mainWindow = Debugger::DebuggerUISwitcher::instance()->mainWindow();
......
......@@ -30,7 +30,9 @@
#define QMLINSPECTORMODE_H
#include "qmlinspector_global.h"
#include "inspectorsettings.h"
#include <coreplugin/basemode.h>
#include <qmlprojectmanager/qmlprojectrunconfiguration.h>
#include <QtGui/QAction>
#include <QtCore/QObject>
......@@ -51,6 +53,9 @@ class QDeclarativeDebugObjectReference;
QT_END_NAMESPACE
namespace ProjectExplorer {
class Project;
}
namespace Core {
class IContext;
......@@ -70,17 +75,27 @@ namespace Qml {
class EngineSpinBox;
}
const int MaxConnectionAttempts = 50;
const int ConnectionAttemptDefaultInterval = 75;
// used when debugging with c++ - connection can take a lot of time
const int ConnectionAttemptSimultaneousInterval = 500;
class QMLINSPECTOR_EXPORT QmlInspector : public QObject
{
Q_OBJECT
public:
QmlInspector(QObject *parent = 0);
~QmlInspector();
void createDockWidgets();
bool connectToViewer(); // using host, port from widgets
Core::IContext *context() const;
// returns false if project is not debuggable.
bool setDebugConfigurationDataFromProject(ProjectExplorer::Project *projectToDebug);
void startConnectionTimer();
signals:
void statusMessage(const QString &text);
......@@ -96,6 +111,10 @@ private slots:
void queryEngineContext(int);
void contextChanged();
void treeObjectActivated(const QDeclarativeDebugObjectReference &obj);
void attachToExternalQmlApplication();
void debuggerStateChanged(int newState);
void pollInspector();
private:
void resetViews();
......@@ -124,8 +143,14 @@ private:
Internal::InspectorContext *m_context;
Internal::InspectorContext *m_propWatcherContext;
QTimer *m_connectionTimer;
int m_connectionAttempts;
Internal::InspectorSettings m_settings;
QmlProjectManager::QmlProjectRunConfigurationDebugData m_runConfigurationDebugData;
};
}
} // Qml
#endif
......@@ -14,16 +14,23 @@ HEADERS += qmlinspectorplugin.h \
qmlinspector.h \
inspectoroutputwidget.h \
qmlinspector_global.h \
inspectorcontext.h
inspectorcontext.h \
startexternalqmldialog.h \
inspectorsettings.h
SOURCES += qmlinspectorplugin.cpp \
qmlinspector.cpp \
inspectoroutputwidget.cpp \
inspectorcontext.cpp
inspectorcontext.cpp \
startexternalqmldialog.cpp \
inspectorsettings.cpp
OTHER_FILES += QmlInspector.pluginspec
RESOURCES += qmlinspector.qrc
FORMS += \
startexternalqmldialog.ui
include(../../qtcreatorplugin.pri)
include(../../plugins/projectexplorer/projectexplorer.pri)
include(../../plugins/qmlprojectmanager/qmlprojectmanager.pri)
......
......@@ -39,18 +39,17 @@ namespace Qml {
const char * const C_INSPECTOR = "QmlInspector";
const char * const COMPLETE_THIS = "QmlInspector.CompleteThis";
const char * const M_ATTACH_TO_EXTERNAL = "QmlInspector.Menu.AttachToExternal";
const char * const LANG_QML = "QML";
};
class StartParameters
{
public:
StartParameters() : port(0) {}
~StartParameters() {}
// settings
const char * const S_QML_INSPECTOR = "QML.Inspector";
const char * const S_EXTERNALPORT_KEY = "ExternalPort";
const char * const S_EXTERNALURL_KEY = "ExternalUrl";
QString address;
quint16 port;
};
};
#endif
......@@ -57,7 +57,6 @@
#include <QtGui/QHBoxLayout>
#include <QtGui/QToolButton>
#include <QtGui/QMessageBox>
#include <QtCore/QDebug>
......@@ -73,10 +72,9 @@ static QToolButton *createToolButton(QAction *action)
}
QmlInspectorPlugin::QmlInspectorPlugin()
: m_inspector(0), m_connectionTimer(new QTimer(this)),
m_connectionAttempts(0)
: m_inspector(0)
{
m_connectionTimer->setInterval(75);
}
QmlInspectorPlugin::~QmlInspectorPlugin()
......@@ -107,8 +105,6 @@ bool QmlInspectorPlugin::initialize(const QStringList &arguments, QString *error
m_inspector->createDockWidgets();
addObject(m_inspector);
connect(m_connectionTimer, SIGNAL(timeout()), SLOT(pollInspector()));
return true;
}
......@@ -148,26 +144,11 @@ void QmlInspectorPlugin::activateDebuggerForProject(ProjectExplorer::Project *pr
// FIXME we probably want to activate the debugger for other projects than QmlProjects,
// if they contain Qml files. Some kind of options should exist for this behavior.
QmlProjectManager::QmlProject *qmlproj = qobject_cast<QmlProjectManager::QmlProject*>(project);
if (qmlproj)
m_connectionTimer->start();
if (qmlproj && m_inspector->setDebugConfigurationDataFromProject(qmlproj))
m_inspector->startConnectionTimer();
}
}
void QmlInspectorPlugin::pollInspector()
{
++m_connectionAttempts;
if (m_inspector->connectToViewer()) {
m_connectionTimer->stop();
m_connectionAttempts = 0;
} else if (m_connectionAttempts == MaxConnectionAttempts) {
m_connectionTimer->stop();