qmljsinspector.cpp 30.4 KB
Newer Older
1
2
3
4
/**************************************************************************
**
** This file is part of Qt Creator
**
con's avatar
con committed
5
** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
6
7
8
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
con's avatar
con committed
9
** No Commercial Usage
10
**
con's avatar
con committed
11
12
13
14
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
15
16
17
18
19
20
21
22
23
24
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
con's avatar
con committed
25
26
27
28
29
30
** In addition, as a special exception, Nokia gives you certain additional
** rights.  These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
31
32
**
**************************************************************************/
33

34
#include "qmljsinspectorconstants.h"
35
#include "qmljsinspector.h"
Lasse Holmstedt's avatar
Lasse Holmstedt committed
36
#include "qmlinspectortoolbar.h"
37
#include "qmljsclientproxy.h"
38
39
#include "qmljslivetextpreview.h"
#include "qmljsprivateapi.h"
40
#include "qmljscontextcrumblepath.h"
41
#include "qmljsinspectorsettings.h"
42
#include "qmljspropertyinspector.h"
43
44
45

#include <qmljs/qmljsmodelmanagerinterface.h>
#include <qmljs/qmljsdocument.h>
46
#include <qmljs/parser/qmljsast_p.h>
Kai Koehne's avatar
Kai Koehne committed
47
48
#include <qmljseditor/qmljseditorconstants.h>
#include <qmljseditor/qmljseditor.h>
49
50
51
52
53
54
#include <debugger/debuggerconstants.h>
#include <debugger/debuggermainwindow.h>
#include <debugger/debuggerplugin.h>

#include <utils/qtcassert.h>
#include <utils/styledbar.h>
55
#include <utils/stylehelper.h>
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74

#include <coreplugin/icontext.h>
#include <coreplugin/findplaceholder.h>
#include <coreplugin/minisplitter.h>
#include <coreplugin/outputpane.h>
#include <coreplugin/rightpane.h>
#include <coreplugin/navigationwidget.h>
#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>
#include <texteditor/basetexteditor.h>

#include <projectexplorer/runconfiguration.h>
75
#include <projectexplorer/buildconfiguration.h>
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#include <projectexplorer/projectexplorer.h>
#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>
#include <QtCore/QtPlugin>
#include <QtCore/QDateTime>

#include <QtGui/QLabel>
#include <QtGui/QDockWidget>
94
#include <QtGui/QVBoxLayout>
95
96
97
#include <QtGui/QAction>
#include <QtGui/QLineEdit>
#include <QtGui/QLabel>
98
#include <QtGui/QPainter>
99
100
101
102
#include <QtGui/QSpinBox>
#include <QtGui/QMessageBox>
#include <QtGui/QTextBlock>

103
104
#include <QtGui/QToolTip>
#include <QtGui/QCursor>
105
106
#include <QtNetwork/QHostAddress>

107
using namespace QmlJS;
108
using namespace QmlJS::AST;
109
110
using namespace QmlJSInspector::Internal;

Roberto Raggi's avatar
Cleanup    
Roberto Raggi committed
111
112
113
114
115
116
117
118
enum {
    MaxConnectionAttempts = 50,
    ConnectionAttemptDefaultInterval = 75,

    // used when debugging with c++ - connection can take a lot of time
    ConnectionAttemptSimultaneousInterval = 500
};

119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
/**
 * A widget that has the base color.
 */
class StyledBackground : public QWidget
{
public:
    explicit StyledBackground(QWidget *parent = 0)
        : QWidget(parent)
    {}

protected:
    void paintEvent(QPaintEvent *e)
    {
        QPainter p(this);
        p.fillRect(e->rect(), Utils::StyleHelper::baseColor());
    }
};

Lasse Holmstedt's avatar
Lasse Holmstedt committed
137
InspectorUi *InspectorUi::m_instance = 0;
138

Lasse Holmstedt's avatar
Lasse Holmstedt committed
139
QmlJS::ModelManagerInterface *modelManager()
140
{
Lasse Holmstedt's avatar
Lasse Holmstedt committed
141
    return ExtensionSystem::PluginManager::instance()->getObject<QmlJS::ModelManagerInterface>();
142
143
}

Lasse Holmstedt's avatar
Lasse Holmstedt committed
144
145
146
InspectorUi::InspectorUi(QObject *parent)
    : QObject(parent)
    , m_listeningToEditorManager(false)
147
    , m_toolBar(0)
148
    , m_crumblePath(0)
149
    , m_filterExp(0)
150
    , m_propertyInspector(0)
Lasse Holmstedt's avatar
Lasse Holmstedt committed
151
152
    , m_settings(new InspectorSettings(this))
    , m_clientProxy(0)
153
154
    , m_qmlEngine(0)
    , m_debugQuery(0)
Lasse Holmstedt's avatar
Lasse Holmstedt committed
155
    , m_debugProject(0)
156
    , m_selectionCallbackExpected(false)
Lasse Holmstedt's avatar
Lasse Holmstedt committed
157
158
{
    m_instance = this;
159
    m_toolBar = new QmlInspectorToolBar(this);
Lasse Holmstedt's avatar
Lasse Holmstedt committed
160
}
161

Lasse Holmstedt's avatar
Lasse Holmstedt committed
162
InspectorUi::~InspectorUi()
163
{
Lasse Holmstedt's avatar
Lasse Holmstedt committed
164
}
165

Lasse Holmstedt's avatar
Lasse Holmstedt committed
166
167
168
169
void InspectorUi::setupUi()
{
    setupDockWidgets();
    restoreSettings();
170
171
}

Lasse Holmstedt's avatar
Lasse Holmstedt committed
172
void InspectorUi::saveSettings() const
173
174
175
176
{
    m_settings->saveSettings(Core::ICore::instance()->settings());
}

Lasse Holmstedt's avatar
Lasse Holmstedt committed
177
void InspectorUi::restoreSettings()
178
179
{
    m_settings->restoreSettings(Core::ICore::instance()->settings());
180
}
181

182
void InspectorUi::setDebuggerEngine(QObject *qmlEngine)
183
184
{
    if (m_qmlEngine && !qmlEngine) {
185
186
        disconnect(m_qmlEngine, SIGNAL(tooltipRequested(QPoint,TextEditor::ITextEditor*,int)),
                   this, SLOT(showDebuggerTooltip(QPoint,TextEditor::ITextEditor*,int)));
187
188
189
190
    }

    m_qmlEngine = qmlEngine;
    if (m_qmlEngine) {
191
192
        connect(m_qmlEngine, SIGNAL(tooltipRequested(QPoint,TextEditor::ITextEditor*,int)),
                this, SLOT(showDebuggerTooltip(QPoint,TextEditor::ITextEditor*,int)));
193
194
195
    }
}

196
QObject *InspectorUi::debuggerEngine() const
197
198
199
200
{
    return m_qmlEngine;
}

Kai Koehne's avatar
Kai Koehne committed
201
202
void InspectorUi::showDebuggerTooltip(const QPoint &mousePos, TextEditor::ITextEditor *editor,
                                      int cursorPos)
203
204
{
    Q_UNUSED(mousePos);
205
    if (m_clientProxy && editor->id() == QmlJSEditor::Constants::C_QMLJSEDITOR_ID) {
Kai Koehne's avatar
Kai Koehne committed
206
207
        QmlJSEditor::QmlJSTextEditor *qmlEditor =
                static_cast<QmlJSEditor::QmlJSTextEditor*>(editor->widget());
208
209
210
211
212
213
214
215
216
217
218
219
220

        QTextCursor tc(qmlEditor->document());
        tc.setPosition(cursorPos);
        tc.movePosition(QTextCursor::StartOfWord);
        tc.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);

        QString wordAtCursor = tc.selectedText();
        QString query;
        QLatin1Char doubleQuote('"');

        QmlJS::AST::Node *qmlNode = qmlEditor->semanticInfo().nodeUnderCursor(cursorPos);
        if (!qmlNode)
            return;
221
222
223
224
225
226
227
228
229
230

        QDeclarativeDebugObjectReference ref;
        if (QmlJS::AST::Node *node
                = qmlEditor->semanticInfo().declaringMemberNoProperties(cursorPos)) {
            if (QmlJS::AST::UiObjectMember *objMember = node->uiObjectMemberCast()) {
                ref = m_clientProxy->objectReferenceForLocation(
                            objMember->firstSourceLocation().startLine,
                            objMember->firstSourceLocation().startColumn);
            }
        }
231
232
233
234
235
236
237
238

        if (ref.debugId() == -1)
            return;

        if (wordAtCursor == QString("id")) {
            query = QString("\"id:") + ref.idString() + doubleQuote;
        } else {
            if ((qmlNode->kind == QmlJS::AST::Node::Kind_IdentifierExpression) ||
Kai Koehne's avatar
Kai Koehne committed
239
                    (qmlNode->kind == QmlJS::AST::Node::Kind_FieldMemberExpression)) {
240
                tc.setPosition(qmlNode->expressionCast()->firstSourceLocation().begin());
Kai Koehne's avatar
Kai Koehne committed
241
242
                tc.setPosition(qmlNode->expressionCast()->lastSourceLocation().end(),
                               QTextCursor::KeepAnchor);
243
244
                QString refToLook = tc.selectedText();
                if ((qmlNode->kind == QmlJS::AST::Node::Kind_IdentifierExpression) &&
Kai Koehne's avatar
Kai Koehne committed
245
                        (m_clientProxy->objectReferenceForId(refToLook).debugId() == -1)) {
246
                    query = doubleQuote + QString("local: ") + refToLook + doubleQuote;
247
                    foreach (QDeclarativeDebugPropertyReference property, ref.properties()) {
Kai Koehne's avatar
Kai Koehne committed
248
249
250
251
                        if (property.name() == wordAtCursor
                                && !property.valueTypeName().isEmpty()) {
                            query = doubleQuote + property.name() + QLatin1Char(':')
                                    + doubleQuote + QLatin1Char('+') + property.name();
252
253
254
255
256
                            break;
                        }
                    }
                }
                else
Kai Koehne's avatar
Kai Koehne committed
257
258
                    query = doubleQuote + refToLook + QLatin1Char(':') + doubleQuote
                            + QLatin1Char('+') + refToLook;
259
260
            } else {
                // show properties
261
                foreach (QDeclarativeDebugPropertyReference property, ref.properties()) {
262
                    if (property.name() == wordAtCursor && !property.valueTypeName().isEmpty()) {
Kai Koehne's avatar
Kai Koehne committed
263
264
                        query = doubleQuote + property.name() + QLatin1Char(':')
                                + doubleQuote + QLatin1Char('+') + property.name();
265
266
267
268
269
270
271
                        break;
                    }
                }
            }
        }

        if (!query.isEmpty()) {
272
            m_debugQuery = m_clientProxy->queryExpressionResult(ref.debugId(),query, this);
Kai Koehne's avatar
Kai Koehne committed
273
274
            connect(m_debugQuery, SIGNAL(stateChanged(QDeclarativeDebugQuery::State)),
                    this, SLOT(debugQueryUpdated(QDeclarativeDebugQuery::State)));
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
        }
    }
}

void InspectorUi::debugQueryUpdated(QDeclarativeDebugQuery::State newState)
{
    if (newState != QDeclarativeDebugExpressionQuery::Completed)
        return;
    if (!m_debugQuery)
        return;

    QString text = m_debugQuery->result().toString();
    if (!text.isEmpty())
        QToolTip::showText(QCursor::pos(), text);

Kai Koehne's avatar
Kai Koehne committed
290
291
    disconnect(m_debugQuery, SIGNAL(stateChanged(QDeclarativeDebugQuery::State)),
               this, SLOT(debugQueryUpdated(QDeclarativeDebugQuery::State)));
292
293
}

294
295
296
297
298
bool InspectorUi::isConnected() const
{
    return m_clientProxy;
}

Lasse Holmstedt's avatar
Lasse Holmstedt committed
299
300
301
302
void InspectorUi::connected(ClientProxy *clientProxy)
{
    m_clientProxy = clientProxy;

303
304
305
306
307
308
309
    QmlJS::Snapshot snapshot = modelManager()->snapshot();
    for (QHash<QString, QmlJSLiveTextPreview *>::const_iterator it = m_textPreviews.constBegin();
         it != m_textPreviews.constEnd(); ++it) {
        Document::Ptr doc = snapshot.document(it.key());
        it.value()->resetInitialDoc(doc);
    }

Lasse Holmstedt's avatar
Lasse Holmstedt committed
310
    m_debugProject = ProjectExplorer::ProjectExplorerPlugin::instance()->startupProject();
311
    if (m_debugProject->activeTarget()
Kai Koehne's avatar
Kai Koehne committed
312
            && m_debugProject->activeTarget()->activeBuildConfiguration())
313
    {
Kai Koehne's avatar
Kai Koehne committed
314
315
        ProjectExplorer::BuildConfiguration *bc
                = m_debugProject->activeTarget()->activeBuildConfiguration();
316
317
318
        m_debugProjectBuildDir = bc->buildDirectory();
    }

Lasse Holmstedt's avatar
Lasse Holmstedt committed
319
    connect(m_debugProject, SIGNAL(destroyed()), SLOT(currentDebugProjectRemoved()));
320
    m_projectFinder.setProjectDirectory(m_debugProject->projectDirectory());
Lasse Holmstedt's avatar
Lasse Holmstedt committed
321

322
323
    connectSignals();
    enable();
Lasse Holmstedt's avatar
Lasse Holmstedt committed
324
325
326
327
328
    resetViews();

    initializeDocuments();

    QHashIterator<QString, QmlJSLiveTextPreview *> iter(m_textPreviews);
329
    while (iter.hasNext()) {
Lasse Holmstedt's avatar
Lasse Holmstedt committed
330
331
        iter.next();
        iter.value()->setClientProxy(m_clientProxy);
332
        iter.value()->updateDebugIds();
Lasse Holmstedt's avatar
Lasse Holmstedt committed
333
334
335
336
    }
}

void InspectorUi::disconnected()
337
{
338
339
    disconnectSignals();
    disable();
Lasse Holmstedt's avatar
Lasse Holmstedt committed
340

341
    m_debugProject = 0;
342
    m_qmlEngine = 0;
343
    resetViews();
Lasse Holmstedt's avatar
Lasse Holmstedt committed
344

345
    applyChangesToQmlObserverHelper(false);
Lasse Holmstedt's avatar
Lasse Holmstedt committed
346
347

    QHashIterator<QString, QmlJSLiveTextPreview *> iter(m_textPreviews);
348
    while (iter.hasNext()) {
Lasse Holmstedt's avatar
Lasse Holmstedt committed
349
350
351
352
        iter.next();
        iter.value()->setClientProxy(0);
    }
    m_clientProxy = 0;
353
    m_propertyInspector->clear();
354
    m_pendingPreviewDocumentNames.clear();
355
356
}

357
358
359
360
361
362
363
364
365
366
void InspectorUi::objectTreeReady()
{
    // Should only run once, after debugger startup
    if (!m_clientProxy->rootObjectReference().isEmpty()) {
        selectItems(m_clientProxy->rootObjectReference());
        disconnect(m_clientProxy, SIGNAL(objectTreeUpdated()),
                   this, SLOT(objectTreeReady()));
    }
}

Lasse Holmstedt's avatar
Lasse Holmstedt committed
367
void InspectorUi::updateEngineList()
368
{
369
    QList<QDeclarativeDebugEngineReference> engines = m_clientProxy->engines();
Roberto Raggi's avatar
Roberto Raggi committed
370

371
//#warning update the QML engines combo
372

373
    if (engines.isEmpty())
374
        qWarning("qmldebugger: no engines found!");
Roberto Raggi's avatar
Roberto Raggi committed
375
    else {
376
        const QDeclarativeDebugEngineReference engine = engines.first();
Roberto Raggi's avatar
Roberto Raggi committed
377
        m_clientProxy->queryEngineContext(engine.debugId());
378
379
380
    }
}

Lasse Holmstedt's avatar
Lasse Holmstedt committed
381
void InspectorUi::changeSelectedItems(const QList<QDeclarativeDebugObjectReference> &objects)
382
{
383
384
385
386
387
388
389
390
391
392
    if (m_selectionCallbackExpected) {
        m_selectionCallbackExpected = false;
        return;
    }

    // QmlJSLiveTextPreview doesn't provide valid references, only correct debugIds. We need to remap them
    QList <QDeclarativeDebugObjectReference> realList;
    foreach (const QDeclarativeDebugObjectReference &obj, objects) {
        QDeclarativeDebugObjectReference clientRef = m_clientProxy->objectReferenceForId(obj.debugId());
        realList <<  clientRef;
393
394
    }

395
396
    m_clientProxy->setSelectedItemsByObjectId(realList);
    selectItems(realList);
397
398
}

Lasse Holmstedt's avatar
Lasse Holmstedt committed
399
void InspectorUi::initializeDocuments()
400
{
Lasse Holmstedt's avatar
Lasse Holmstedt committed
401
    if (!modelManager() || !m_clientProxy)
402
403
404
        return;

    Core::EditorManager *em = Core::EditorManager::instance();
405
406
407
408
    m_loadedSnapshot = modelManager()->snapshot();

    if (!m_listeningToEditorManager) {
        m_listeningToEditorManager = true;
Kai Koehne's avatar
Kai Koehne committed
409
410
411
412
        connect(em, SIGNAL(editorAboutToClose(Core::IEditor*)),
                this, SLOT(removePreviewForEditor(Core::IEditor*)));
        connect(em, SIGNAL(editorOpened(Core::IEditor*)),
                this, SLOT(createPreviewForEditor(Core::IEditor*)));
413
414
        connect(modelManager(),
                SIGNAL(documentChangedOnDisk(QmlJS::Document::Ptr)),
Kai Koehne's avatar
Kai Koehne committed
415
                this, SLOT(updatePendingPreviewDocuments(QmlJS::Document::Ptr)));
416
    }
417
418

    // initial update
419
420
421
    foreach (Core::IEditor *editor, em->openedEditors()) {
        createPreviewForEditor(editor);
    }
422
423

    applyChangesToQmlObserverHelper(true);
424
425
}

Lasse Holmstedt's avatar
Lasse Holmstedt committed
426
void InspectorUi::serverReloaded()
427
428
429
430
431
432
433
434
{
    QmlJS::Snapshot snapshot = modelManager()->snapshot();
    m_loadedSnapshot = snapshot;
    for (QHash<QString, QmlJSLiveTextPreview *>::const_iterator it = m_textPreviews.constBegin();
         it != m_textPreviews.constEnd(); ++it) {
        Document::Ptr doc = snapshot.document(it.key());
        it.value()->resetInitialDoc(doc);
    }
435
    m_clientProxy->refreshObjectTree();
436
437
438
}


Lasse Holmstedt's avatar
Lasse Holmstedt committed
439
void InspectorUi::removePreviewForEditor(Core::IEditor *oldEditor)
440
441
442
443
444
445
{
    if (QmlJSLiveTextPreview *preview = m_textPreviews.value(oldEditor->file()->fileName())) {
        preview->unassociateEditor(oldEditor);
    }
}

446
QmlJSLiveTextPreview *InspectorUi::createPreviewForEditor(Core::IEditor *newEditor)
447
{
448
449
    QmlJSLiveTextPreview *preview = 0;

Lasse Holmstedt's avatar
Lasse Holmstedt committed
450
    if (m_clientProxy
Kai Koehne's avatar
Kai Koehne committed
451
452
453
454
            && m_clientProxy->isConnected()
            && newEditor
            && newEditor->id() == QmlJSEditor::Constants::C_QMLJSEDITOR_ID
            )
455
    {
456
457
        QString filename = newEditor->file()->fileName();
        QmlJS::Document::Ptr doc = modelManager()->snapshot().document(filename);
458
459
460
461
462
463
464
465
466
467
468
        if (!doc) {
            if (filename.endsWith(".qml")) {
                // add to list of docs that we have to update when
                // snapshot figures out that there's a new document
                m_pendingPreviewDocumentNames.append(filename);
            }
            return 0;
        }
        if (!doc->qmlProgram())
            return 0;

469
470
471
        QmlJS::Document::Ptr initdoc = m_loadedSnapshot.document(filename);
        if (!initdoc)
            initdoc = doc;
472
473

        if (m_textPreviews.contains(filename)) {
474
475
            preview = m_textPreviews.value(filename);
            preview->associateEditor(newEditor);
476
        } else {
477
            preview = new QmlJSLiveTextPreview(doc, initdoc, m_clientProxy, this);
478
            connect(preview,
Kai Koehne's avatar
Kai Koehne committed
479
                    SIGNAL(selectedItemsChanged(QList<QDeclarativeDebugObjectReference>)),
480
                    SLOT(changeSelectedItems(QList<QDeclarativeDebugObjectReference>)));
Kai Koehne's avatar
Kai Koehne committed
481
482
            connect(preview, SIGNAL(reloadQmlViewerRequested()),
                    m_clientProxy, SLOT(reloadQmlViewer()));
483
484
            connect(preview, SIGNAL(disableLivePreviewRequested()), SLOT(disableLivePreview()));

485
            m_textPreviews.insert(newEditor->file()->fileName(), preview);
486
            preview->associateEditor(newEditor);
487
            preview->updateDebugIds();
488
489
        }
    }
490
491

    return preview;
492
493
}

Lasse Holmstedt's avatar
Lasse Holmstedt committed
494
void InspectorUi::currentDebugProjectRemoved()
495
496
497
498
{
    m_debugProject = 0;
}

Lasse Holmstedt's avatar
Lasse Holmstedt committed
499
void InspectorUi::resetViews()
500
{
501
502
    m_propertyInspector->clear();
    m_crumblePath->clear();
503
504
}

Lasse Holmstedt's avatar
Lasse Holmstedt committed
505
void InspectorUi::reloadQmlViewer()
506
{
Lasse Holmstedt's avatar
Lasse Holmstedt committed
507
508
    if (m_clientProxy)
        m_clientProxy->reloadQmlViewer();
509
510
}

511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
inline QDeclarativeDebugObjectReference findParentRecursive( int goalDebugId,
                                                            const QList< QDeclarativeDebugObjectReference > &objectsToSearch)
{
    if (goalDebugId == -1)
        return QDeclarativeDebugObjectReference();

    foreach (const QDeclarativeDebugObjectReference &possibleParent, objectsToSearch) {
        // Am I a root object? No parent
        if ( possibleParent.debugId() == goalDebugId )
            return QDeclarativeDebugObjectReference();

        // Is the goal one of my children?
        foreach (const QDeclarativeDebugObjectReference &child, possibleParent.children())
            if ( child.debugId() == goalDebugId )
                return possibleParent;

        // no luck? pass this on
        QDeclarativeDebugObjectReference candidate = findParentRecursive(goalDebugId, possibleParent.children());
        if (candidate.debugId() != -1)
            return candidate;
    }

    return QDeclarativeDebugObjectReference();
}

void InspectorUi::selectItems(const QList<QDeclarativeDebugObjectReference> &objectReferences)
537
{
538
    foreach (const QDeclarativeDebugObjectReference &objref, objectReferences) {
539
540
541
542
543
544
545
546
547
548
549
550
        if (objref.debugId() != -1) {
            // select only the first valid element of the list

            m_clientProxy->removeAllObjectWatches();
            m_clientProxy->addObjectWatch(objref.debugId());
            QList <QDeclarativeDebugObjectReference> selectionList;
            selectionList << objref;
            m_propertyInspector->setCurrentObjects(selectionList);
            populateCrumblePath(objref);
            gotoObjectReferenceDefinition(objref);
            return;
        }
551
    }
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
}

inline QString displayName(const QDeclarativeDebugObjectReference &obj)
{
    // special! state names
    if (obj.className() == "State") {
        foreach (const QDeclarativeDebugPropertyReference &prop, obj.properties()) {
            if (prop.name() == "name")
                return prop.value().toString();
        }
    }

    // has id?
    if (!obj.idString().isEmpty())
        return obj.idString();

    // return the simplified class name then
    QString objTypeName = obj.className();
    QString declarativeString("QDeclarative");
    if (objTypeName.startsWith(declarativeString)) {
        objTypeName = objTypeName.mid(declarativeString.length()).section('_',0,0);
    }
    return QString("<%1>").arg(objTypeName);
}

bool InspectorUi::isRoot(const QDeclarativeDebugObjectReference &obj) const
{
    foreach (const QDeclarativeDebugObjectReference &rootObj, m_clientProxy->rootObjectReference())
        if (obj.debugId() == rootObj.debugId())
            return true;
    return false;
}

void InspectorUi::populateCrumblePath(const QDeclarativeDebugObjectReference &objRef)
{
    QStringList crumbleStrings;
    QList <int> crumbleData;

    // first find path by climbing the hierarchy
    QDeclarativeDebugObjectReference ref = objRef;
    crumbleData << objRef.debugId();
    crumbleStrings << displayName(objRef);

    while ((!isRoot(ref)) && (ref.debugId()!=-1)) {
        ref = findParentRecursive(ref.debugId(), m_clientProxy->rootObjectReference());
        crumbleData.push_front( ref.debugId() );
        crumbleStrings.push_front( displayName(ref) );
    }

601
602
603
    m_crumblePath->updateContextPath(crumbleStrings, crumbleData);
    crumbleStrings.clear();
    crumbleData.clear();
604
605
606
607
608
609
610

    // now append the children
    foreach (const QDeclarativeDebugObjectReference &child, objRef.children()) {
        crumbleData.push_back(child.debugId());
        crumbleStrings.push_back( displayName(child) );
    }

611
    m_crumblePath->addChildren(crumbleStrings, crumbleData);
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
}

void InspectorUi::selectItems(const QList<int> &objectIds)
{
    QList<QDeclarativeDebugObjectReference> objectReferences;
    foreach (int objectId, objectIds)
    {
        QDeclarativeDebugObjectReference ref = m_clientProxy->objectReferenceForId(objectId);
        if (ref.debugId() == objectId)
            objectReferences.append(ref);
    }
    if (objectReferences.length() > 0)
        selectItems(objectReferences);
}

void InspectorUi::changePropertyValue(int debugId,const QString &propertyName, const QString &valueExpression)
{
    QString query = propertyName + '=' + valueExpression;
630
    m_clientProxy->queryExpressionResult(debugId, query, this);
631
632
}

633
634
void InspectorUi::enable()
{
635
    m_toolBar->enable();
636
    m_crumblePath->setEnabled(true);
637
    m_propertyInspector->setEnabled(true);
638
639
640
641
}

void InspectorUi::disable()
{
642
    m_toolBar->disable();
643
    m_crumblePath->setEnabled(false);
644
    m_propertyInspector->setEnabled(false);
645
646
}

647
QDeclarativeDebugObjectReference InspectorUi::objectReferenceForLocation(const QString &fileName, int cursorPosition) const
648
{
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
    Core::EditorManager *editorManager = Core::EditorManager::instance();
    Core::IEditor *editor = editorManager->openEditor(fileName);
    TextEditor::ITextEditor *textEditor = qobject_cast<TextEditor::ITextEditor*>(editor);

    if (textEditor && m_clientProxy && textEditor->id() == QmlJSEditor::Constants::C_QMLJSEDITOR_ID) {
        if (cursorPosition == -1)
            cursorPosition = textEditor->position();
        QmlJSEditor::QmlJSTextEditor *qmlEditor =
                static_cast<QmlJSEditor::QmlJSTextEditor*>(textEditor->widget());

        if (QmlJS::AST::Node *node
                = qmlEditor->semanticInfo().declaringMemberNoProperties(cursorPosition)) {
            if (QmlJS::AST::UiObjectMember *objMember = node->uiObjectMemberCast()) {
                return m_clientProxy->objectReferenceForLocation(
                            objMember->firstSourceLocation().startLine,
                            objMember->firstSourceLocation().startColumn);
            }
        }
    }
    return QDeclarativeDebugObjectReference();
}
670

671
672
void InspectorUi::gotoObjectReferenceDefinition(const QDeclarativeDebugObjectReference &obj)
{
673
    QDeclarativeDebugFileReference source = obj.source();
674
    QString fileName = source.url().toLocalFile();
675
676
677
678

    if (source.lineNumber() < 0 || !QFile::exists(fileName))
        return;

679
    fileName = m_projectFinder.findFile(fileName);
680

681
    Core::EditorManager *editorManager = Core::EditorManager::instance();
682
    Core::IEditor *currentEditor = editorManager->currentEditor();
683
    Core::IEditor *editor = editorManager->openEditor(fileName);
684
685
    TextEditor::ITextEditor *textEditor = qobject_cast<TextEditor::ITextEditor*>(editor);

686
687
688
    if (currentEditor != editor)
            m_selectionCallbackExpected = true;

689
    if (textEditor) {
690
        QDeclarativeDebugObjectReference ref = objectReferenceForLocation(fileName);
Thorbjørn Lindeijer's avatar
Thorbjørn Lindeijer committed
691
        if (ref.debugId() != obj.debugId()) {
692
693
694
695
696
            m_selectionCallbackExpected = true;
            editorManager->addCurrentPositionToNavigationHistory();
            textEditor->gotoLine(source.lineNumber());
            textEditor->widget()->setFocus();
        }
697
698
699
    }
}

Lasse Holmstedt's avatar
Lasse Holmstedt committed
700
bool InspectorUi::addQuotesForData(const QVariant &value) const
701
702
703
704
705
706
707
708
709
710
711
712
{
    switch (value.type()) {
    case QVariant::String:
    case QVariant::Color:
    case QVariant::Date:
        return true;
    default:
        break;
    }

    return false;
}
713

Lasse Holmstedt's avatar
Lasse Holmstedt committed
714
void InspectorUi::setupDockWidgets()
715
{
716
717
    Debugger::DebuggerMainWindow *mw = Debugger::DebuggerPlugin::mainWindow();

718
719
    m_toolBar->createActions(Core::Context(Debugger::Constants::C_QMLDEBUGGER));
    m_toolBar->setObjectName("QmlInspectorToolbar");
720
    mw->setToolBar(Debugger::QmlLanguage, m_toolBar->widget());
721

722
    m_crumblePath = new ContextCrumblePath;
723
    m_crumblePath->setObjectName("QmlContextPath");
724
    m_crumblePath->setWindowTitle(tr("Context Path"));
725
    connect(m_crumblePath, SIGNAL(elementClicked(int)), SLOT(crumblePathElementClicked(int)));
726

727
    m_propertyInspector = new QmlJSPropertyInspector;
728

729
730
    QWidget *observerWidget = new QWidget;
    observerWidget->setWindowTitle(tr("QML Observer"));
hjk's avatar
hjk committed
731
    observerWidget->setObjectName(Debugger::Constants::DOCKWIDGET_QML_INSPECTOR);
732

733
734
735
736
    QWidget *pathAndFilterWidget = new StyledBackground;
    pathAndFilterWidget->setMaximumHeight(m_crumblePath->height());

    m_filterExp = new QLineEdit;
737
    m_filterExp->setPlaceholderText(tr("Filter properties"));
738
739
740
741
742
743
744
745
    m_filterExp->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);

    QHBoxLayout *pathAndFilterLayout = new QHBoxLayout(pathAndFilterWidget);
    pathAndFilterLayout->setMargin(0);
    pathAndFilterLayout->setSpacing(0);
    pathAndFilterLayout->addWidget(m_crumblePath);
    pathAndFilterLayout->addWidget(m_filterExp);

746
    QVBoxLayout *wlay = new QVBoxLayout(observerWidget);
747
748
    wlay->setMargin(0);
    wlay->setSpacing(0);
749
    observerWidget->setLayout(wlay);
750
    wlay->addWidget(pathAndFilterWidget);
751
752
    wlay->addWidget(m_propertyInspector);

hjk's avatar
hjk committed
753
    QDockWidget *dock = mw->createDockWidget(Debugger::QmlLanguage, observerWidget);
754
755
    dock->setAllowedAreas(Qt::TopDockWidgetArea | Qt::BottomDockWidgetArea);
    dock->setTitleBarWidget(new QWidget(dock));
756
757
}

758
void InspectorUi::crumblePathElementClicked(int debugId)
759
{
Thorbjørn Lindeijer's avatar
Thorbjørn Lindeijer committed
760
    QList<int> l;
761
    l << debugId;
762
    selectItems(l);
763
764
}

Lasse Holmstedt's avatar
Lasse Holmstedt committed
765
bool InspectorUi::showExperimentalWarning()
766
{
767
    return m_settings->showLivePreviewWarning();
768
769
}

Lasse Holmstedt's avatar
Lasse Holmstedt committed
770
void InspectorUi::setShowExperimentalWarning(bool value)
771
{
772
    m_settings->setShowLivePreviewWarning(value);
773
774
}

Lasse Holmstedt's avatar
Lasse Holmstedt committed
775
InspectorUi *InspectorUi::instance()
776
777
778
779
{
    return m_instance;
}

Lasse Holmstedt's avatar
Lasse Holmstedt committed
780
ProjectExplorer::Project *InspectorUi::debugProject() const
781
782
783
784
{
    return m_debugProject;
}

785
786
bool InspectorUi::isShadowBuildProject() const
{
787
788
    // for .qmlproject based stuff, build dir is empty
    if (!debugProject() || debugProjectBuildDirectory().isEmpty())
789
790
791
792
793
794
795
796
797
798
        return false;

    return (debugProject()->projectDirectory() != debugProjectBuildDirectory());
}

QString InspectorUi::debugProjectBuildDirectory() const
{
    return m_debugProjectBuildDir;
}

Lasse Holmstedt's avatar
Lasse Holmstedt committed
799
void InspectorUi::setApplyChangesToQmlObserver(bool applyChanges)
800
801
802
803
804
{
    emit livePreviewActivated(applyChanges);
    applyChangesToQmlObserverHelper(applyChanges);
}

Lasse Holmstedt's avatar
Lasse Holmstedt committed
805
void InspectorUi::applyChangesToQmlObserverHelper(bool applyChanges)
806
807
{
    QHashIterator<QString, QmlJSLiveTextPreview *> iter(m_textPreviews);
808
    while (iter.hasNext()) {
809
810
811
812
813
        iter.next();
        iter.value()->setApplyChangesToQmlObserver(applyChanges);
    }
}

814
815
816
817
818
819
820
821
void InspectorUi::updatePendingPreviewDocuments(QmlJS::Document::Ptr doc)
{
    int idx = -1;
    idx = m_pendingPreviewDocumentNames.indexOf(doc->fileName());

    if (idx == -1)
        return;

Kai Koehne's avatar
Kai Koehne committed
822
823
    QList<Core::IEditor *> editors
            = Core::EditorManager::instance()->editorsForFileName(doc->fileName());
824
825
826
827
828
829
830
831
832

    if (editors.isEmpty())
        return;

    m_pendingPreviewDocumentNames.removeAt(idx);

    QmlJSLiveTextPreview *preview = createPreviewForEditor(editors.first());
    editors.removeFirst();

833
    foreach (Core::IEditor *editor, editors)
834
835
836
        preview->associateEditor(editor);
}

Lasse Holmstedt's avatar
Lasse Holmstedt committed
837
void InspectorUi::disableLivePreview()
838
839
840
{
    setApplyChangesToQmlObserver(false);
}
Lasse Holmstedt's avatar
Lasse Holmstedt committed
841

842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
void InspectorUi::connectSignals()
{
    connect(m_propertyInspector, SIGNAL(changePropertyValue(int,QString,QString)),
            this, SLOT(changePropertyValue(int,QString,QString)));

    connect(m_clientProxy, SIGNAL(propertyChanged(int,QByteArray,QVariant)),
            m_propertyInspector, SLOT(propertyValueChanged(int,QByteArray,QVariant)));

    connect(m_clientProxy, SIGNAL(selectedItemsChanged(QList<QDeclarativeDebugObjectReference>)),
            this, SLOT(selectItems(QList<QDeclarativeDebugObjectReference>)));
    connect(m_clientProxy, SIGNAL(enginesChanged()),
            this, SLOT(updateEngineList()));
    connect(m_clientProxy, SIGNAL(serverReloaded()),
            this, SLOT(serverReloaded()));
    connect(m_clientProxy, SIGNAL(objectTreeUpdated()),
            this, SLOT(objectTreeReady()));
    connect(m_clientProxy, SIGNAL(connected()),
            this, SLOT(enable()));
    connect(m_clientProxy, SIGNAL(disconnected()),
            this, SLOT(disable()));

    connect(m_clientProxy, SIGNAL(colorPickerActivated()),
            m_toolBar, SLOT(activateColorPicker()));
    connect(m_clientProxy, SIGNAL(selectToolActivated()),
            m_toolBar, SLOT(activateSelectTool()));
    connect(m_clientProxy, SIGNAL(zoomToolActivated()),
            m_toolBar, SLOT(activateZoomTool()));
    connect(m_clientProxy, SIGNAL(designModeBehaviorChanged(bool)),
            m_toolBar, SLOT(setDesignModeBehavior(bool)));
    connect(m_clientProxy, SIGNAL(showAppOnTopChanged(bool)),
            m_toolBar, SLOT(setShowAppOnTop(bool)));
    connect(m_clientProxy, SIGNAL(selectedColorChanged(QColor)),
            m_toolBar, SLOT(setSelectedColor(QColor)));
    connect(m_clientProxy, SIGNAL(animationSpeedChanged(qreal)),
            m_toolBar, SLOT(setAnimationSpeed(qreal)));

    connect(m_toolBar, SIGNAL(applyChangesFromQmlFileTriggered(bool)),
            this, SLOT(setApplyChangesToQmlObserver(bool)));

    connect(m_toolBar, SIGNAL(designModeSelected(bool)),
            m_clientProxy, SLOT(setDesignModeBehavior(bool)));
    connect(m_toolBar, SIGNAL(reloadSelected()),
            m_clientProxy, SLOT(reloadQmlViewer()));
    connect(m_toolBar, SIGNAL(animationSpeedChanged(qreal)),
            m_clientProxy, SLOT(setAnimationSpeed(qreal)));
    connect(m_toolBar, SIGNAL(colorPickerSelected()),
            m_clientProxy, SLOT(changeToColorPickerTool()));
    connect(m_toolBar, SIGNAL(zoomToolSelected()),
            m_clientProxy, SLOT(changeToZoomTool()));
    connect(m_toolBar, SIGNAL(selectToolSelected()),
            m_clientProxy, SLOT(changeToSelectTool()));
    connect(m_toolBar, SIGNAL(showAppOnTopSelected(bool)),
            m_clientProxy, SLOT(showAppOnTop(bool)));

896
    connect(m_filterExp, SIGNAL(textChanged(QString)),
897
898
899
900
901
902
903
904
905
906
907
908
909
910
            m_propertyInspector, SLOT(filterBy(QString)));
}

void InspectorUi::disconnectSignals()
{
    m_propertyInspector->disconnect(this);

    m_clientProxy->disconnect(m_propertyInspector);
    m_clientProxy->disconnect(this);
    m_clientProxy->disconnect(m_toolBar);

    m_toolBar->disconnect(this);
    m_toolBar->disconnect(m_clientProxy);
    m_toolBar->disconnect(m_propertyInspector);
Lasse Holmstedt's avatar
Lasse Holmstedt committed
911
}