1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
11 ** Licensees holding valid Qt Commercial licenses may use this file in
12 ** accordance with the Qt Commercial License Agreement provided with the
13 ** Software or, alternatively, in accordance with the terms contained in
14 ** a written agreement between you and Nokia.
16 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 ** If you are unsure which license is appropriate for your use, please
26 ** contact the sales department at http://qt.nokia.com/contact.
28 **************************************************************************/
29 #include "qmljsinspectorconstants.h"
30 #include "qmljsinspector.h"
31 #include "qmljsclientproxy.h"
32 #include "qmljsinspectorcontext.h"
33 #include "qmljsdelta.h"
34 #include "qmljslivetextpreview.h"
35 #include "qmljsprivateapi.h"
37 #include <qmljs/qmljsmodelmanagerinterface.h>
38 #include <qmljs/qmljsdocument.h>
40 #include <debugger/debuggerrunner.h>
41 #include <debugger/debuggerconstants.h>
42 #include <debugger/debuggerengine.h>
43 #include <debugger/debuggermainwindow.h>
44 #include <debugger/debuggerplugin.h>
45 #include <debugger/debuggerrunner.h>
46 #include <debugger/debuggeruiswitcher.h>
47 #include <debugger/debuggerconstants.h>
49 #include <utils/qtcassert.h>
50 #include <utils/styledbar.h>
51 #include <utils/fancymainwindow.h>
53 #include <coreplugin/icontext.h>
54 #include <coreplugin/basemode.h>
55 #include <coreplugin/findplaceholder.h>
56 #include <coreplugin/minisplitter.h>
57 #include <coreplugin/outputpane.h>
58 #include <coreplugin/rightpane.h>
59 #include <coreplugin/navigationwidget.h>
60 #include <coreplugin/icore.h>
61 #include <coreplugin/coreconstants.h>
62 #include <coreplugin/uniqueidmanager.h>
63 #include <coreplugin/actionmanager/actioncontainer.h>
64 #include <coreplugin/actionmanager/actionmanager.h>
65 #include <coreplugin/actionmanager/command.h>
66 #include <coreplugin/editormanager/editormanager.h>
68 #include <texteditor/itexteditor.h>
69 #include <texteditor/basetexteditor.h>
71 #include <projectexplorer/runconfiguration.h>
72 #include <projectexplorer/projectexplorer.h>
73 #include <projectexplorer/projectexplorerconstants.h>
74 #include <projectexplorer/project.h>
75 #include <projectexplorer/target.h>
76 #include <projectexplorer/applicationrunconfiguration.h>
77 #include <qmlprojectmanager/qmlprojectconstants.h>
78 #include <qmlprojectmanager/qmlprojectrunconfiguration.h>
80 #include <extensionsystem/pluginmanager.h>
82 #include <QtCore/QDebug>
83 #include <QtCore/QStringList>
84 #include <QtCore/QTimer>
85 #include <QtCore/QtPlugin>
86 #include <QtCore/QDateTime>
88 #include <QtGui/QLabel>
89 #include <QtGui/QDockWidget>
90 #include <QtGui/QAction>
91 #include <QtGui/QLineEdit>
92 #include <QtGui/QLabel>
93 #include <QtGui/QSpinBox>
94 #include <QtGui/QMessageBox>
95 #include <QtGui/QTextBlock>
97 #include <QtNetwork/QHostAddress>
99 using namespace QmlJS;
100 using namespace QmlJS::AST;
101 using namespace QmlJSInspector::Internal;
102 using namespace Debugger::Internal;
110 MaxConnectionAttempts = 50,
111 ConnectionAttemptDefaultInterval = 75,
113 // used when debugging with c++ - connection can take a lot of time
114 ConnectionAttemptSimultaneousInterval = 500
117 Inspector::Inspector(QObject *parent)
119 m_connectionTimer(new QTimer(this)),
120 m_connectionAttempts(0),
121 m_cppDebuggerState(0),
122 m_simultaneousCppAndQmlDebugMode(false),
123 m_debugMode(StandaloneMode)
125 m_clientProxy = ClientProxy::instance();
127 //#warning set up the context widget
128 QWidget *contextWidget = 0;
129 m_context = new InspectorContext(contextWidget);
131 m_textPreview = new QmlJSLiveTextPreview(this);
133 connect(m_textPreview,
134 SIGNAL(selectedItemsChanged(QList<QDeclarativeDebugObjectReference>)),
135 SLOT(changeSelectedItems(QList<QDeclarativeDebugObjectReference>)));
137 connect(m_clientProxy, SIGNAL(selectedItemsChanged(QList<QDeclarativeDebugObjectReference>)),
138 SLOT(setSelectedItemsByObjectReference(QList<QDeclarativeDebugObjectReference>)));
140 connect(m_clientProxy, SIGNAL(connectionStatusMessage(QString)), SIGNAL(statusMessage(QString)));
141 connect(m_clientProxy, SIGNAL(connected(QDeclarativeEngineDebug*)), SLOT(connected(QDeclarativeEngineDebug*)));
142 connect(m_clientProxy, SIGNAL(disconnected()), SLOT(disconnected()));
143 connect(m_clientProxy, SIGNAL(aboutToReloadEngines()), SLOT(aboutToReloadEngines()));
144 connect(m_clientProxy, SIGNAL(enginesChanged()), SLOT(updateEngineList()));
145 connect(m_clientProxy, SIGNAL(aboutToDisconnect()), SLOT(disconnectWidgets()));
146 connect(m_clientProxy, SIGNAL(objectTreeUpdated(QDeclarativeDebugObjectReference)), SLOT(objectTreeUpdated(QDeclarativeDebugObjectReference)));
148 connect(Debugger::DebuggerPlugin::instance(),
149 SIGNAL(stateChanged(int)), this, SLOT(debuggerStateChanged(int)));
151 connect(m_connectionTimer, SIGNAL(timeout()), SLOT(pollInspector()));
155 Inspector::~Inspector()
159 void Inspector::disconnectWidgets()
163 void Inspector::disconnected()
169 void Inspector::aboutToReloadEngines()
173 void Inspector::updateEngineList()
175 const QList<QDeclarativeDebugEngineReference> engines = m_clientProxy->engines();
177 //#warning update the QML engines combo
179 if (engines.isEmpty())
180 qWarning("qmldebugger: no engines found!");
182 const QDeclarativeDebugEngineReference engine = engines.first();
183 m_clientProxy->queryEngineContext(engine.debugId());
187 void Inspector::changeSelectedItems(const QList<QDeclarativeDebugObjectReference> &objects)
189 m_clientProxy->setSelectedItemsByObjectId(objects);
192 void Inspector::shutdown()
194 //#warning save the inspector settings here
197 void Inspector::pollInspector()
199 ++m_connectionAttempts;
201 const QString host = m_runConfigurationDebugData.serverAddress;
202 const quint16 port = quint16(m_runConfigurationDebugData.serverPort);
204 if (m_clientProxy->connectToViewer(host, port)) {
205 m_textPreview->updateDocuments();
206 m_connectionTimer->stop();
207 m_connectionAttempts = 0;
208 } else if (m_connectionAttempts == MaxConnectionAttempts) {
209 m_connectionTimer->stop();
210 m_connectionAttempts = 0;
212 QMessageBox::critical(0,
213 tr("Failed to connect to debugger"),
214 tr("Could not connect to debugger server.") );
219 bool Inspector::setDebugConfigurationDataFromProject(ProjectExplorer::Project *projectToDebug)
221 if (!projectToDebug) {
222 emit statusMessage(tr("Invalid project, debugging canceled."));
226 QmlProjectManager::QmlProjectRunConfiguration* config =
227 qobject_cast<QmlProjectManager::QmlProjectRunConfiguration*>(projectToDebug->activeTarget()->activeRunConfiguration());
229 emit statusMessage(tr("Cannot find project run configuration, debugging canceled."));
232 m_runConfigurationDebugData.serverAddress = config->debugServerAddress();
233 m_runConfigurationDebugData.serverPort = config->debugServerPort();
234 m_connectionTimer->setInterval(ConnectionAttemptDefaultInterval);
239 void Inspector::startQmlProjectDebugger()
241 m_simultaneousCppAndQmlDebugMode = false;
242 m_connectionTimer->start();
245 void Inspector::resetViews()
247 //#warning reset the views here
250 void Inspector::simultaneouslyDebugQmlCppApplication()
252 QString errorMessage;
253 ProjectExplorer::ProjectExplorerPlugin *pex = ProjectExplorer::ProjectExplorerPlugin::instance();
254 ProjectExplorer::Project *project = pex->startupProject();
257 errorMessage = tr("No project was found.");
258 else if (project->id() == QLatin1String("QmlProjectManager.QmlProject"))
259 errorMessage = attachToQmlViewerAsExternalApp(project);
261 errorMessage = attachToExternalCppAppWithQml(project);
263 if (!errorMessage.isEmpty())
264 QMessageBox::warning(Core::ICore::instance()->mainWindow(), tr("Failed to debug C++ and QML"), errorMessage);
267 QString Inspector::attachToQmlViewerAsExternalApp(ProjectExplorer::Project *project)
272 //#warning implement attachToQmlViewerAsExternalApp
277 m_debugMode = QmlProjectWithCppPlugins;
279 QmlProjectManager::QmlProjectRunConfiguration* runConfig =
280 qobject_cast<QmlProjectManager::QmlProjectRunConfiguration*>(project->activeTarget()->activeRunConfiguration());
283 return tr("No run configurations were found for the project '%1'.").arg(project->displayName());
285 Internal::StartExternalQmlDialog dlg(Debugger::DebuggerUISwitcher::instance()->mainWindow());
287 QString importPathArgument = "-I";
289 if (runConfig->viewerArguments().contains(importPathArgument))
290 execArgs = runConfig->viewerArguments().join(" ");
292 QFileInfo qmlFileInfo(runConfig->viewerArguments().last());
293 importPathArgument.append(" " + qmlFileInfo.absolutePath() + " ");
294 execArgs = importPathArgument + runConfig->viewerArguments().join(" ");
298 dlg.setPort(runConfig->debugServerPort());
299 dlg.setDebuggerUrl(runConfig->debugServerAddress());
300 dlg.setProjectDisplayName(project->displayName());
301 dlg.setDebugMode(Internal::StartExternalQmlDialog::QmlProjectWithCppPlugins);
302 dlg.setQmlViewerArguments(execArgs);
303 dlg.setQmlViewerPath(runConfig->viewerPath());
305 if (dlg.exec() != QDialog::Accepted)
308 m_runConfigurationDebugData.serverAddress = dlg.debuggerUrl();
309 m_runConfigurationDebugData.serverPort = dlg.port();
310 m_settings.setExternalPort(dlg.port());
311 m_settings.setExternalUrl(dlg.debuggerUrl());
313 ProjectExplorer::Environment customEnv = ProjectExplorer::Environment::systemEnvironment(); // empty env by default
314 customEnv.set(QmlProjectManager::Constants::E_QML_DEBUG_SERVER_PORT, QString::number(m_settings.externalPort()));
316 Debugger::DebuggerRunControl *debuggableRunControl =
317 createDebuggerRunControl(runConfig, dlg.qmlViewerPath(), dlg.qmlViewerArguments());
319 return executeDebuggerRunControl(debuggableRunControl, &customEnv);
323 QString Inspector::attachToExternalCppAppWithQml(ProjectExplorer::Project *project)
326 //#warning implement attachToExternalCppAppWithQml
331 m_debugMode = CppProjectWithQmlEngines;
333 ProjectExplorer::LocalApplicationRunConfiguration* runConfig =
334 qobject_cast<ProjectExplorer::LocalApplicationRunConfiguration*>(project->activeTarget()->activeRunConfiguration());
336 if (!project->activeTarget() || !project->activeTarget()->activeRunConfiguration())
337 return tr("No run configurations were found for the project '%1'.").arg(project->displayName());
339 return tr("No valid run configuration was found for the project %1. "
340 "Only locally runnable configurations are supported.\n"
341 "Please check your project settings.").arg(project->displayName());
343 Internal::StartExternalQmlDialog dlg(Debugger::DebuggerUISwitcher::instance()->mainWindow());
345 dlg.setPort(m_settings.externalPort());
346 dlg.setDebuggerUrl(m_settings.externalUrl());
347 dlg.setProjectDisplayName(project->displayName());
348 dlg.setDebugMode(Internal::StartExternalQmlDialog::CppProjectWithQmlEngine);
349 if (dlg.exec() != QDialog::Accepted)
352 m_runConfigurationDebugData.serverAddress = dlg.debuggerUrl();
353 m_runConfigurationDebugData.serverPort = dlg.port();
354 m_settings.setExternalPort(dlg.port());
355 m_settings.setExternalUrl(dlg.debuggerUrl());
357 ProjectExplorer::Environment customEnv = runConfig->environment();
358 customEnv.set(QmlProjectManager::Constants::E_QML_DEBUG_SERVER_PORT, QString::number(m_settings.externalPort()));
359 Debugger::DebuggerRunControl *debuggableRunControl = createDebuggerRunControl(runConfig);
360 return executeDebuggerRunControl(debuggableRunControl, &customEnv);
364 QString Inspector::executeDebuggerRunControl(Debugger::DebuggerRunControl *debuggableRunControl,
365 ProjectExplorer::Environment *environment)
367 Q_UNUSED(debuggableRunControl);
368 Q_UNUSED(environment);
369 ProjectExplorer::ProjectExplorerPlugin *pex = ProjectExplorer::ProjectExplorerPlugin::instance();
371 // to make sure we have a valid, debuggable run control, find the correct factory for it
372 if (debuggableRunControl) {
375 debuggableRunControl->setCustomEnvironment(*environment);
377 pex->startRunControl(debuggableRunControl, ProjectExplorer::Constants::DEBUGMODE);
378 m_simultaneousCppAndQmlDebugMode = true;
382 return tr("A valid run control was not registered in Qt Creator for this project run configuration.");
385 Debugger::DebuggerRunControl *Inspector::createDebuggerRunControl(ProjectExplorer::RunConfiguration *runConfig,
386 const QString &executableFile,
387 const QString &executableArguments)
389 ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
390 const QList<Debugger::DebuggerRunControlFactory *> factories = pm->getObjects<Debugger::DebuggerRunControlFactory>();
391 ProjectExplorer::RunControl *runControl = 0;
393 if (m_debugMode == QmlProjectWithCppPlugins) {
394 Debugger::DebuggerStartParameters sp;
395 sp.startMode = Debugger::StartExternal;
396 sp.executable = executableFile;
397 sp.processArgs = executableArguments.split(QLatin1Char(' '));
398 runControl = factories.first()->create(sp);
399 return qobject_cast<Debugger::DebuggerRunControl *>(runControl);
402 if (m_debugMode == CppProjectWithQmlEngines) {
403 if (factories.length() && factories.first()->canRun(runConfig, ProjectExplorer::Constants::DEBUGMODE)) {
404 runControl = factories.first()->create(runConfig, ProjectExplorer::Constants::DEBUGMODE);
405 return qobject_cast<Debugger::DebuggerRunControl *>(runControl);
412 void Inspector::connected(QDeclarativeEngineDebug *client)
418 void Inspector::updateMenuActions()
421 if (m_simultaneousCppAndQmlDebugMode)
422 enabled = (m_cppDebuggerState == Debugger::DebuggerNotReady && m_clientProxy->isUnconnected());
424 enabled = m_clientProxy->isUnconnected();
427 void Inspector::debuggerStateChanged(int newState)
429 if (m_simultaneousCppAndQmlDebugMode) {
431 case Debugger::EngineStarting:
433 m_connectionInitialized = false;
436 case Debugger::EngineStartFailed:
437 case Debugger::InferiorStartFailed:
438 emit statusMessage(tr("Debugging failed: could not start C++ debugger."));
440 case Debugger::InferiorRunningRequested:
442 if (m_cppDebuggerState == Debugger::InferiorStopped) {
443 // re-enable UI again
444 //#warning enable the UI here
448 case Debugger::InferiorRunning:
450 if (!m_connectionInitialized) {
451 m_connectionInitialized = true;
452 m_connectionTimer->setInterval(ConnectionAttemptSimultaneousInterval);
453 m_connectionTimer->start();
457 case Debugger::InferiorStopped:
459 //#warning disable the UI here
462 case Debugger::EngineShuttingDown:
464 m_connectionInitialized = false;
465 // here it's safe to enable the debugger windows again -
466 // disabled ones look ugly.
467 //#warning enable the UI here
468 m_simultaneousCppAndQmlDebugMode = false;
476 m_cppDebuggerState = newState;
480 void Inspector::reloadQmlViewer()
482 m_clientProxy->reloadQmlViewer();
485 void Inspector::setSimpleDockWidgetArrangement()
488 Utils::FancyMainWindow *mainWindow = Debugger::DebuggerUISwitcher::instance()->mainWindow();
490 mainWindow->setTrackingEnabled(false);
491 QList<QDockWidget *> dockWidgets = mainWindow->dockWidgets();
492 foreach (QDockWidget *dockWidget, dockWidgets) {
493 if (m_dockWidgets.contains(dockWidget)) {
494 dockWidget->setFloating(false);
495 mainWindow->removeDockWidget(dockWidget);
499 foreach (QDockWidget *dockWidget, dockWidgets) {
500 if (m_dockWidgets.contains(dockWidget)) {
501 mainWindow->addDockWidget(Qt::BottomDockWidgetArea, dockWidget);
505 mainWindow->splitDockWidget(mainWindow->toolBarDockWidget(), m_propertyWatcherDock, Qt::Vertical);
506 //mainWindow->tabifyDockWidget(m_frameRateDock, m_propertyWatcherDock);
507 mainWindow->tabifyDockWidget(m_propertyWatcherDock, m_expressionQueryDock);
508 mainWindow->tabifyDockWidget(m_propertyWatcherDock, m_inspectorOutputDock);
509 m_propertyWatcherDock->raise();
511 m_inspectorOutputDock->setVisible(false);
513 mainWindow->setTrackingEnabled(true);
517 void Inspector::setSelectedItemsByObjectReference(QList<QDeclarativeDebugObjectReference> objectReferences)
519 if (objectReferences.length())
520 gotoObjectReferenceDefinition(objectReferences.first());
523 void Inspector::gotoObjectReferenceDefinition(const QDeclarativeDebugObjectReference &obj)
527 QDeclarativeDebugFileReference source = obj.source();
528 const QString fileName = source.url().toLocalFile();
530 if (source.lineNumber() < 0 || !QFile::exists(fileName))
533 Core::EditorManager *editorManager = Core::EditorManager::instance();
534 Core::IEditor *editor = editorManager->openEditor(fileName, QString(), Core::EditorManager::NoModeSwitch);
535 TextEditor::ITextEditor *textEditor = qobject_cast<TextEditor::ITextEditor*>(editor);
538 editorManager->addCurrentPositionToNavigationHistory();
539 textEditor->gotoLine(source.lineNumber());
540 textEditor->widget()->setFocus();
544 QDeclarativeDebugExpressionQuery *Inspector::executeExpression(int objectDebugId, const QString &objectId,
545 const QString &propertyName, const QVariant &value)
547 if (objectId.length()) {
548 QString quoteWrappedValue = value.toString();
549 if (addQuotesForData(value))
550 quoteWrappedValue = QString("'%1'").arg(quoteWrappedValue); // ### FIXME this code is wrong!
552 QString constructedExpression = objectId + "." + propertyName + "=" + quoteWrappedValue;
553 return m_client.data()->queryExpressionResult(objectDebugId, constructedExpression, this);
559 bool Inspector::addQuotesForData(const QVariant &value) const
561 switch (value.type()) {
562 case QVariant::String:
563 case QVariant::Color:
574 Associates the UiObjectMember* to their QDeclarativeDebugObjectReference.
576 class MapObjectWithDebugReference : public Visitor
579 virtual void endVisit(UiObjectDefinition *ast) ;
580 virtual void endVisit(UiObjectBinding *ast) ;
582 QDeclarativeDebugObjectReference root;
584 QHash<UiObjectMember *, QList<QDeclarativeDebugObjectReference> > result;
586 void processRecursive(const QDeclarativeDebugObjectReference &object, UiObjectMember *ast);
589 void MapObjectWithDebugReference::endVisit(UiObjectDefinition* ast)
591 if (ast->qualifiedTypeNameId->name->asString().at(0).isUpper())
592 processRecursive(root, ast);
594 void MapObjectWithDebugReference::endVisit(UiObjectBinding* ast)
596 if (ast->qualifiedId->name->asString().at(0).isUpper())
597 processRecursive(root, ast);
600 void MapObjectWithDebugReference::processRecursive(const QDeclarativeDebugObjectReference& object, UiObjectMember* ast)
602 // If this is too slow, it can be speed up by indexing
603 // the QDeclarativeDebugObjectReference by filename/loc in a fist pass
605 SourceLocation loc = ast->firstSourceLocation();
606 if (object.source().lineNumber() == int(loc.startLine) && object.source().columnNumber() == int(loc.startColumn) && object.source().url().toLocalFile() == filename) {
607 result[ast] += object;
610 foreach (const QDeclarativeDebugObjectReference &it, object.children()) {
611 processRecursive(it, ast);
615 void QmlJSInspector::Internal::Inspector::objectTreeUpdated(const QDeclarativeDebugObjectReference &ref)
617 QmlJS::ModelManagerInterface *m = QmlJS::ModelManagerInterface::instance();
618 Snapshot snapshot = m->snapshot();
619 QHash<QString, QHash<UiObjectMember *, QList< QDeclarativeDebugObjectReference> > > allDebugIds;
620 foreach(const Document::Ptr &doc, snapshot) {
621 if (!doc->qmlProgram())
623 MapObjectWithDebugReference visitor;
625 QString filename = doc->fileName();
626 visitor.filename = filename;
627 doc->qmlProgram()->accept(&visitor);
628 allDebugIds[filename] = visitor.result;
632 m_textPreview->m_initialTable = allDebugIds;
633 m_textPreview->m_debugIds.clear();