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 <qmljseditor/qmljseditorconstants.h>
39 #include <qmljs/qmljsmodelmanagerinterface.h>
40 #include <qmljs/qmljsdocument.h>
42 #include <debugger/debuggerrunner.h>
43 #include <debugger/debuggerconstants.h>
44 #include <debugger/debuggerengine.h>
45 #include <debugger/debuggermainwindow.h>
46 #include <debugger/debuggerplugin.h>
47 #include <debugger/debuggerrunner.h>
48 #include <debugger/debuggeruiswitcher.h>
49 #include <debugger/debuggerconstants.h>
51 #include <utils/qtcassert.h>
52 #include <utils/styledbar.h>
53 #include <utils/fancymainwindow.h>
55 #include <coreplugin/icontext.h>
56 #include <coreplugin/basemode.h>
57 #include <coreplugin/findplaceholder.h>
58 #include <coreplugin/minisplitter.h>
59 #include <coreplugin/outputpane.h>
60 #include <coreplugin/rightpane.h>
61 #include <coreplugin/navigationwidget.h>
62 #include <coreplugin/icore.h>
63 #include <coreplugin/coreconstants.h>
64 #include <coreplugin/uniqueidmanager.h>
65 #include <coreplugin/actionmanager/actioncontainer.h>
66 #include <coreplugin/actionmanager/actionmanager.h>
67 #include <coreplugin/actionmanager/command.h>
68 #include <coreplugin/editormanager/editormanager.h>
70 #include <texteditor/itexteditor.h>
71 #include <texteditor/basetexteditor.h>
73 #include <projectexplorer/runconfiguration.h>
74 #include <projectexplorer/projectexplorer.h>
75 #include <projectexplorer/projectexplorerconstants.h>
76 #include <projectexplorer/project.h>
77 #include <projectexplorer/target.h>
78 #include <projectexplorer/applicationrunconfiguration.h>
79 #include <qmlprojectmanager/qmlprojectconstants.h>
80 #include <qmlprojectmanager/qmlprojectrunconfiguration.h>
82 #include <extensionsystem/pluginmanager.h>
84 #include <QtCore/QDebug>
85 #include <QtCore/QStringList>
86 #include <QtCore/QTimer>
87 #include <QtCore/QtPlugin>
88 #include <QtCore/QDateTime>
90 #include <QtGui/QLabel>
91 #include <QtGui/QDockWidget>
92 #include <QtGui/QAction>
93 #include <QtGui/QLineEdit>
94 #include <QtGui/QLabel>
95 #include <QtGui/QSpinBox>
96 #include <QtGui/QMessageBox>
97 #include <QtGui/QTextBlock>
99 #include <QtNetwork/QHostAddress>
101 using namespace QmlJS;
102 using namespace QmlJS::AST;
103 using namespace QmlJSInspector::Internal;
104 using namespace Debugger::Internal;
112 MaxConnectionAttempts = 50,
113 ConnectionAttemptDefaultInterval = 75,
115 // used when debugging with c++ - connection can take a lot of time
116 ConnectionAttemptSimultaneousInterval = 500
119 Inspector::Inspector(QObject *parent)
121 m_connectionTimer(new QTimer(this)),
122 m_connectionAttempts(0),
123 m_cppDebuggerState(0),
124 m_simultaneousCppAndQmlDebugMode(false),
125 m_debugMode(StandaloneMode)
127 m_clientProxy = ClientProxy::instance();
129 //#warning set up the context widget
130 QWidget *contextWidget = 0;
131 m_context = new InspectorContext(contextWidget);
133 connect(m_clientProxy, SIGNAL(selectedItemsChanged(QList<QDeclarativeDebugObjectReference>)),
134 SLOT(setSelectedItemsByObjectReference(QList<QDeclarativeDebugObjectReference>)));
136 connect(m_clientProxy, SIGNAL(connectionStatusMessage(QString)), SIGNAL(statusMessage(QString)));
137 connect(m_clientProxy, SIGNAL(connected(QDeclarativeEngineDebug*)), SLOT(connected(QDeclarativeEngineDebug*)));
138 connect(m_clientProxy, SIGNAL(disconnected()), SLOT(disconnected()));
139 connect(m_clientProxy, SIGNAL(aboutToReloadEngines()), SLOT(aboutToReloadEngines()));
140 connect(m_clientProxy, SIGNAL(enginesChanged()), SLOT(updateEngineList()));
141 connect(m_clientProxy, SIGNAL(aboutToDisconnect()), SLOT(disconnectWidgets()));
142 connect(m_clientProxy, SIGNAL(serverReloaded()), this, SLOT(serverReloaded()));
144 connect(Debugger::DebuggerPlugin::instance(),
145 SIGNAL(stateChanged(int)), this, SLOT(debuggerStateChanged(int)));
147 connect(m_connectionTimer, SIGNAL(timeout()), SLOT(pollInspector()));
151 Inspector::~Inspector()
155 void Inspector::disconnectWidgets()
159 void Inspector::disconnected()
161 Core::EditorManager *em = Core::EditorManager::instance();
162 disconnect(em, SIGNAL(editorAboutToClose(Core::IEditor*)), this, SLOT(removePreviewForEditor(Core::IEditor*)));
163 disconnect(em, SIGNAL(editorOpened(Core::IEditor*)), this, SLOT(createPreviewForEditor(Core::IEditor*)));
168 void Inspector::aboutToReloadEngines()
172 void Inspector::updateEngineList()
174 const QList<QDeclarativeDebugEngineReference> engines = m_clientProxy->engines();
176 //#warning update the QML engines combo
178 if (engines.isEmpty())
179 qWarning("qmldebugger: no engines found!");
181 const QDeclarativeDebugEngineReference engine = engines.first();
182 m_clientProxy->queryEngineContext(engine.debugId());
186 void Inspector::changeSelectedItems(const QList<QDeclarativeDebugObjectReference> &objects)
188 m_clientProxy->setSelectedItemsByObjectId(objects);
191 void Inspector::shutdown()
193 //#warning save the inspector settings here
196 void Inspector::pollInspector()
198 ++m_connectionAttempts;
200 const QString host = m_runConfigurationDebugData.serverAddress;
201 const quint16 port = quint16(m_runConfigurationDebugData.serverPort);
203 if (m_clientProxy->connectToViewer(host, port)) {
204 initializeDocuments();
205 m_connectionTimer->stop();
206 m_connectionAttempts = 0;
207 } else if (m_connectionAttempts == MaxConnectionAttempts) {
208 m_connectionTimer->stop();
209 m_connectionAttempts = 0;
211 QMessageBox::critical(0,
212 tr("Failed to connect to debugger"),
213 tr("Could not connect to debugger server.") );
218 QmlJS::ModelManagerInterface *Inspector::modelManager()
220 return ExtensionSystem::PluginManager::instance()->getObject<QmlJS::ModelManagerInterface>();
223 void Inspector::initializeDocuments()
228 m_loadedSnapshot = modelManager()->snapshot();
229 Core::EditorManager *em = Core::EditorManager::instance();
230 connect(em, SIGNAL(editorAboutToClose(Core::IEditor*)), SLOT(removePreviewForEditor(Core::IEditor*)));
231 connect(em, SIGNAL(editorOpened(Core::IEditor*)), SLOT(createPreviewForEditor(Core::IEditor*)));
234 foreach (Core::IEditor *editor, em->openedEditors()) {
235 createPreviewForEditor(editor);
239 void Inspector::serverReloaded()
241 QmlJS::Snapshot snapshot = modelManager()->snapshot();
242 m_loadedSnapshot = snapshot;
243 for (QHash<QString, QmlJSLiveTextPreview *>::const_iterator it = m_textPreviews.constBegin();
244 it != m_textPreviews.constEnd(); ++it) {
245 Document::Ptr doc = snapshot.document(it.key());
246 it.value()->resetInitialDoc(doc);
248 ClientProxy::instance()->queryEngineContext(0);
249 //ClientProxy::instance()->refreshObjectTree();
253 void Inspector::removePreviewForEditor(Core::IEditor *oldEditor)
255 if (QmlJSLiveTextPreview *preview = m_textPreviews.value(oldEditor->file()->fileName())) {
256 preview->unassociateEditor(oldEditor);
260 void Inspector::createPreviewForEditor(Core::IEditor *newEditor)
262 if (newEditor && newEditor->id() == QmlJSEditor::Constants::C_QMLJSEDITOR_ID) {
263 QString filename = newEditor->file()->fileName();
264 QmlJS::Document::Ptr doc = modelManager()->snapshot().document(filename);
265 if (!doc || !doc->qmlProgram())
267 QmlJS::Document::Ptr initdoc = m_loadedSnapshot.document(filename);
271 if (m_textPreviews.contains(filename)) {
272 m_textPreviews.value(filename)->associateEditor(newEditor);
274 QmlJSLiveTextPreview *preview = new QmlJSLiveTextPreview(doc, initdoc, this);
276 SIGNAL(selectedItemsChanged(QList<QDeclarativeDebugObjectReference>)),
277 SLOT(changeSelectedItems(QList<QDeclarativeDebugObjectReference>)));
278 m_textPreviews.insert(newEditor->file()->fileName(), preview);
279 preview->updateDebugIds(m_clientProxy->rootObjectReference());
284 bool Inspector::setDebugConfigurationDataFromProject(ProjectExplorer::Project *projectToDebug)
286 if (!projectToDebug) {
287 emit statusMessage(tr("Invalid project, debugging canceled."));
291 QmlProjectManager::QmlProjectRunConfiguration* config =
292 qobject_cast<QmlProjectManager::QmlProjectRunConfiguration*>(projectToDebug->activeTarget()->activeRunConfiguration());
294 emit statusMessage(tr("Cannot find project run configuration, debugging canceled."));
297 m_runConfigurationDebugData.serverAddress = config->debugServerAddress();
298 m_runConfigurationDebugData.serverPort = config->debugServerPort();
299 m_connectionTimer->setInterval(ConnectionAttemptDefaultInterval);
304 void Inspector::startQmlProjectDebugger()
306 m_simultaneousCppAndQmlDebugMode = false;
307 m_connectionTimer->start();
310 void Inspector::resetViews()
312 //#warning reset the views here
315 void Inspector::simultaneouslyDebugQmlCppApplication()
317 QString errorMessage;
318 ProjectExplorer::ProjectExplorerPlugin *pex = ProjectExplorer::ProjectExplorerPlugin::instance();
319 ProjectExplorer::Project *project = pex->startupProject();
322 errorMessage = tr("No project was found.");
323 else if (project->id() == QLatin1String("QmlProjectManager.QmlProject"))
324 errorMessage = attachToQmlViewerAsExternalApp(project);
326 errorMessage = attachToExternalCppAppWithQml(project);
328 if (!errorMessage.isEmpty())
329 QMessageBox::warning(Core::ICore::instance()->mainWindow(), tr("Failed to debug C++ and QML"), errorMessage);
332 QString Inspector::attachToQmlViewerAsExternalApp(ProjectExplorer::Project *project)
337 //#warning implement attachToQmlViewerAsExternalApp
342 m_debugMode = QmlProjectWithCppPlugins;
344 QmlProjectManager::QmlProjectRunConfiguration* runConfig =
345 qobject_cast<QmlProjectManager::QmlProjectRunConfiguration*>(project->activeTarget()->activeRunConfiguration());
348 return tr("No run configurations were found for the project '%1'.").arg(project->displayName());
350 Internal::StartExternalQmlDialog dlg(Debugger::DebuggerUISwitcher::instance()->mainWindow());
352 QString importPathArgument = "-I";
354 if (runConfig->viewerArguments().contains(importPathArgument))
355 execArgs = runConfig->viewerArguments().join(" ");
357 QFileInfo qmlFileInfo(runConfig->viewerArguments().last());
358 importPathArgument.append(" " + qmlFileInfo.absolutePath() + " ");
359 execArgs = importPathArgument + runConfig->viewerArguments().join(" ");
363 dlg.setPort(runConfig->debugServerPort());
364 dlg.setDebuggerUrl(runConfig->debugServerAddress());
365 dlg.setProjectDisplayName(project->displayName());
366 dlg.setDebugMode(Internal::StartExternalQmlDialog::QmlProjectWithCppPlugins);
367 dlg.setQmlViewerArguments(execArgs);
368 dlg.setQmlViewerPath(runConfig->viewerPath());
370 if (dlg.exec() != QDialog::Accepted)
373 m_runConfigurationDebugData.serverAddress = dlg.debuggerUrl();
374 m_runConfigurationDebugData.serverPort = dlg.port();
375 m_settings.setExternalPort(dlg.port());
376 m_settings.setExternalUrl(dlg.debuggerUrl());
378 ProjectExplorer::Environment customEnv = ProjectExplorer::Environment::systemEnvironment(); // empty env by default
379 customEnv.set(QmlProjectManager::Constants::E_QML_DEBUG_SERVER_PORT, QString::number(m_settings.externalPort()));
381 Debugger::DebuggerRunControl *debuggableRunControl =
382 createDebuggerRunControl(runConfig, dlg.qmlViewerPath(), dlg.qmlViewerArguments());
384 return executeDebuggerRunControl(debuggableRunControl, &customEnv);
388 QString Inspector::attachToExternalCppAppWithQml(ProjectExplorer::Project *project)
391 //#warning implement attachToExternalCppAppWithQml
396 m_debugMode = CppProjectWithQmlEngines;
398 ProjectExplorer::LocalApplicationRunConfiguration* runConfig =
399 qobject_cast<ProjectExplorer::LocalApplicationRunConfiguration*>(project->activeTarget()->activeRunConfiguration());
401 if (!project->activeTarget() || !project->activeTarget()->activeRunConfiguration())
402 return tr("No run configurations were found for the project '%1'.").arg(project->displayName());
404 return tr("No valid run configuration was found for the project %1. "
405 "Only locally runnable configurations are supported.\n"
406 "Please check your project settings.").arg(project->displayName());
408 Internal::StartExternalQmlDialog dlg(Debugger::DebuggerUISwitcher::instance()->mainWindow());
410 dlg.setPort(m_settings.externalPort());
411 dlg.setDebuggerUrl(m_settings.externalUrl());
412 dlg.setProjectDisplayName(project->displayName());
413 dlg.setDebugMode(Internal::StartExternalQmlDialog::CppProjectWithQmlEngine);
414 if (dlg.exec() != QDialog::Accepted)
417 m_runConfigurationDebugData.serverAddress = dlg.debuggerUrl();
418 m_runConfigurationDebugData.serverPort = dlg.port();
419 m_settings.setExternalPort(dlg.port());
420 m_settings.setExternalUrl(dlg.debuggerUrl());
422 ProjectExplorer::Environment customEnv = runConfig->environment();
423 customEnv.set(QmlProjectManager::Constants::E_QML_DEBUG_SERVER_PORT, QString::number(m_settings.externalPort()));
424 Debugger::DebuggerRunControl *debuggableRunControl = createDebuggerRunControl(runConfig);
425 return executeDebuggerRunControl(debuggableRunControl, &customEnv);
429 QString Inspector::executeDebuggerRunControl(Debugger::DebuggerRunControl *debuggableRunControl,
430 ProjectExplorer::Environment *environment)
432 Q_UNUSED(debuggableRunControl);
433 Q_UNUSED(environment);
434 ProjectExplorer::ProjectExplorerPlugin *pex = ProjectExplorer::ProjectExplorerPlugin::instance();
436 // to make sure we have a valid, debuggable run control, find the correct factory for it
437 if (debuggableRunControl) {
440 debuggableRunControl->setCustomEnvironment(*environment);
442 pex->startRunControl(debuggableRunControl, ProjectExplorer::Constants::DEBUGMODE);
443 m_simultaneousCppAndQmlDebugMode = true;
447 return tr("A valid run control was not registered in Qt Creator for this project run configuration.");
450 Debugger::DebuggerRunControl *Inspector::createDebuggerRunControl(ProjectExplorer::RunConfiguration *runConfig,
451 const QString &executableFile,
452 const QString &executableArguments)
454 ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
455 const QList<Debugger::DebuggerRunControlFactory *> factories = pm->getObjects<Debugger::DebuggerRunControlFactory>();
456 ProjectExplorer::RunControl *runControl = 0;
458 if (m_debugMode == QmlProjectWithCppPlugins) {
459 Debugger::DebuggerStartParameters sp;
460 sp.startMode = Debugger::StartExternal;
461 sp.executable = executableFile;
462 sp.processArgs = executableArguments.split(QLatin1Char(' '));
463 runControl = factories.first()->create(sp);
464 return qobject_cast<Debugger::DebuggerRunControl *>(runControl);
467 if (m_debugMode == CppProjectWithQmlEngines) {
468 if (factories.length() && factories.first()->canRun(runConfig, ProjectExplorer::Constants::DEBUGMODE)) {
469 runControl = factories.first()->create(runConfig, ProjectExplorer::Constants::DEBUGMODE);
470 return qobject_cast<Debugger::DebuggerRunControl *>(runControl);
477 void Inspector::connected(QDeclarativeEngineDebug *client)
483 void Inspector::updateMenuActions()
486 if (m_simultaneousCppAndQmlDebugMode)
487 enabled = (m_cppDebuggerState == Debugger::DebuggerNotReady && m_clientProxy->isUnconnected());
489 enabled = m_clientProxy->isUnconnected();
492 void Inspector::debuggerStateChanged(int newState)
495 // FIXME: AAA: adjsut to new debugger states
496 if (m_simultaneousCppAndQmlDebugMode) {
498 case Debugger::EngineStarting:
500 m_connectionInitialized = false;
503 case Debugger::EngineStartFailed:
504 case Debugger::InferiorStartFailed:
505 emit statusMessage(tr("Debugging failed: could not start C++ debugger."));
507 case Debugger::InferiorRunningRequested:
509 if (m_cppDebuggerState == Debugger::InferiorStopped) {
510 // re-enable UI again
511 //#warning enable the UI here
515 case Debugger::InferiorRunning:
517 if (!m_connectionInitialized) {
518 m_connectionInitialized = true;
519 m_connectionTimer->setInterval(ConnectionAttemptSimultaneousInterval);
520 m_connectionTimer->start();
524 case Debugger::InferiorStopped:
526 //#warning disable the UI here
529 case Debugger::EngineShuttingDown:
531 m_connectionInitialized = false;
532 // here it's safe to enable the debugger windows again -
533 // disabled ones look ugly.
534 //#warning enable the UI here
535 m_simultaneousCppAndQmlDebugMode = false;
543 m_cppDebuggerState = newState;
547 void Inspector::reloadQmlViewer()
549 m_clientProxy->reloadQmlViewer();
552 void Inspector::setSimpleDockWidgetArrangement()
555 Utils::FancyMainWindow *mainWindow = Debugger::DebuggerUISwitcher::instance()->mainWindow();
557 mainWindow->setTrackingEnabled(false);
558 QList<QDockWidget *> dockWidgets = mainWindow->dockWidgets();
559 foreach (QDockWidget *dockWidget, dockWidgets) {
560 if (m_dockWidgets.contains(dockWidget)) {
561 dockWidget->setFloating(false);
562 mainWindow->removeDockWidget(dockWidget);
566 foreach (QDockWidget *dockWidget, dockWidgets) {
567 if (m_dockWidgets.contains(dockWidget)) {
568 mainWindow->addDockWidget(Qt::BottomDockWidgetArea, dockWidget);
572 mainWindow->splitDockWidget(mainWindow->toolBarDockWidget(), m_propertyWatcherDock, Qt::Vertical);
573 //mainWindow->tabifyDockWidget(m_frameRateDock, m_propertyWatcherDock);
574 mainWindow->tabifyDockWidget(m_propertyWatcherDock, m_expressionQueryDock);
575 mainWindow->tabifyDockWidget(m_propertyWatcherDock, m_inspectorOutputDock);
576 m_propertyWatcherDock->raise();
578 m_inspectorOutputDock->setVisible(false);
580 mainWindow->setTrackingEnabled(true);
584 void Inspector::setSelectedItemsByObjectReference(QList<QDeclarativeDebugObjectReference> objectReferences)
586 if (objectReferences.length())
587 gotoObjectReferenceDefinition(objectReferences.first());
590 void Inspector::gotoObjectReferenceDefinition(const QDeclarativeDebugObjectReference &obj)
594 QDeclarativeDebugFileReference source = obj.source();
595 const QString fileName = source.url().toLocalFile();
597 if (source.lineNumber() < 0 || !QFile::exists(fileName))
600 Core::EditorManager *editorManager = Core::EditorManager::instance();
601 Core::IEditor *editor = editorManager->openEditor(fileName, QString(), Core::EditorManager::NoModeSwitch);
602 TextEditor::ITextEditor *textEditor = qobject_cast<TextEditor::ITextEditor*>(editor);
605 editorManager->addCurrentPositionToNavigationHistory();
606 textEditor->gotoLine(source.lineNumber());
607 textEditor->widget()->setFocus();
611 QDeclarativeDebugExpressionQuery *Inspector::executeExpression(int objectDebugId, const QString &objectId,
612 const QString &propertyName, const QVariant &value)
614 if (objectId.length()) {
615 QString quoteWrappedValue = value.toString();
616 if (addQuotesForData(value))
617 quoteWrappedValue = QString("'%1'").arg(quoteWrappedValue); // ### FIXME this code is wrong!
619 QString constructedExpression = objectId + "." + propertyName + "=" + quoteWrappedValue;
620 return m_client.data()->queryExpressionResult(objectDebugId, constructedExpression, this);
626 bool Inspector::addQuotesForData(const QVariant &value) const
628 switch (value.type()) {
629 case QVariant::String:
630 case QVariant::Color: