qmllivetextpreview.cpp 26.3 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
Olivier Goffart's avatar
Olivier Goffart committed
2
**
3
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
hjk's avatar
hjk committed
4
** Contact: http://www.qt-project.org/legal
Olivier Goffart's avatar
Olivier Goffart committed
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
Olivier Goffart's avatar
Olivier Goffart committed
7
**
hjk's avatar
hjk committed
8
9
10
11
12
13
14
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
Olivier Goffart's avatar
Olivier Goffart committed
15
16
**
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17
18
19
20
21
22
23
24
25
** 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.
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
con's avatar
con committed
26
27
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
28
****************************************************************************/
Olivier Goffart's avatar
Olivier Goffart committed
29

30
#include "qmllivetextpreview.h"
31

32
33
#include "qmlinspectoradapter.h"
#include "qmlinspectoragent.h"
34

35
36
#include <coreplugin/infobar.h>
#include <qmldebug/basetoolsclient.h>
37
#include <qmljseditor/qmljseditorconstants.h>
38
#include <qmljs/parser/qmljsast_p.h>
39
40
#include <qmljs/qmljsdelta.h>
#include <qmljs/qmljsmodelmanagerinterface.h>
41
42
#include <utils/qtcassert.h>

Kai Koehne's avatar
Kai Koehne committed
43
using namespace QmlDebug;
44
using namespace QmlJS;
45
46
using namespace QmlJS::AST;

47
namespace Debugger {
48
49
namespace Internal {

50
51
const char INFO_OUT_OF_SYNC[] = "Debugger.Inspector.OutOfSyncWarning";

52
53
54
55
56
/*!
   Associates the UiObjectMember* to their QDeclarativeDebugObjectReference.
 */
class MapObjectWithDebugReference : public Visitor
{
57
58
59
public:
    typedef QList<int> DebugIdList;
    MapObjectWithDebugReference() : activated(0) {}
60
61
62
63
    virtual void endVisit(UiObjectDefinition *ast);
    virtual void endVisit(UiObjectBinding *ast);
    virtual bool visit(UiObjectDefinition *ast);
    virtual bool visit(UiObjectBinding *ast);
64
65
66
67
68
69
70
71
72
73
74

    QHash<QPair<int, int>, DebugIdList> ids;
    QString filename;
    QHash<UiObjectMember *, DebugIdList> result;
    QSet<UiObjectMember *> lookupObjects;

private:
    void process(UiObjectMember *ast);
    void process(UiObjectBinding *ast);
private:
    int activated;
75
76
};

77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
class UpdateInspector : public Delta {
private:
    static inline QString stripQuotes(const QString &str)
    {
        if ((str.startsWith(QLatin1Char('"')) && str.endsWith(QLatin1Char('"')))
                || (str.startsWith(QLatin1Char('\''))
                    && str.endsWith(QLatin1Char('\''))))
            return str.mid(1, str.length() - 2);

        return str;
    }

    static inline QString deEscape(const QString &value)
    {
        QString result = value;

        result.replace(QLatin1String("\\\\"), QLatin1String("\\"));
        result.replace(QLatin1String("\\\""), QLatin1String("\""));
        result.replace(QLatin1String("\\\t"), QLatin1String("\t"));
        result.replace(QLatin1String("\\\r"), QLatin1String("\\\r"));
        result.replace(QLatin1String("\\\n"), QLatin1String("\n"));

        return result;
    }

    static QString cleanExpression(const QString &expression,
                                   UiScriptBinding *scriptBinding)
    {
        QString trimmedExpression = expression.trimmed();

        if (ExpressionStatement *expStatement
                = cast<ExpressionStatement*>(scriptBinding->statement)) {
            if (expStatement->semicolonToken.isValid())
                trimmedExpression.chop(1);
        }

        return deEscape(stripQuotes(trimmedExpression));
    }

    static bool isLiteralValue(ExpressionNode *expr)
    {
        if (cast<NumericLiteral*>(expr))
            return true;
        else if (cast<StringLiteral*>(expr))
            return true;
        else if (UnaryPlusExpression *plusExpr
                 = cast<UnaryPlusExpression*>(expr))
            return isLiteralValue(plusExpr->expression);
        else if (UnaryMinusExpression *minusExpr
                 = cast<UnaryMinusExpression*>(expr))
            return isLiteralValue(minusExpr->expression);
        else if (cast<TrueLiteral*>(expr))
            return true;
        else if (cast<FalseLiteral*>(expr))
            return true;
        else
            return false;
    }

    static inline bool isLiteralValue(UiScriptBinding *script)
    {
        if (!script || !script->statement)
            return false;

        ExpressionStatement *exprStmt
                = cast<ExpressionStatement *>(script->statement);
        if (exprStmt)
            return isLiteralValue(exprStmt->expression);
        else
            return false;
    }

    static QVariant castToLiteral(const QString &expression,
                                  UiScriptBinding *scriptBinding)
    {
        const QString cleanedValue = cleanExpression(expression, scriptBinding);
        QVariant castedExpression;

        ExpressionStatement *expStatement
                = cast<ExpressionStatement*>(scriptBinding->statement);

        switch (expStatement->expression->kind) {
        case Node::Kind_NumericLiteral:
        case Node::Kind_UnaryPlusExpression:
        case Node::Kind_UnaryMinusExpression:
            castedExpression = QVariant(cleanedValue).toReal();
            break;
        case Node::Kind_StringLiteral:
            castedExpression = QVariant(cleanedValue).toString();
            break;
        case Node::Kind_TrueLiteral:
        case Node::Kind_FalseLiteral:
            castedExpression = QVariant(cleanedValue).toBool();
            break;
        default:
            castedExpression = cleanedValue;
            break;
        }

        return castedExpression;
    }

protected:
    virtual void updateMethodBody(DebugId debugId,
                                  UiObjectMember *parentDefinition,
                                  UiScriptBinding *scriptBinding,
                                  const QString &methodName,
                                  const QString &methodBody)
    {
        Q_UNUSED(scriptBinding);
        Q_UNUSED(parentDefinition);
        appliedChangesToViewer = true;
189
190
191
        if (m_inspectorAdapter->engineClient())
            m_inspectorAdapter->engineClient()->setMethodBody(debugId,
                                                              methodName, methodBody);
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
    }

    virtual void updateScriptBinding(DebugId debugId,
                                     UiObjectMember *parentDefinition,
                                     UiScriptBinding *scriptBinding,
                                     const QString &propertyName,
                                     const QString &scriptCode)
    {
        if (unsyncronizableChanges
                == QmlLiveTextPreview::NoUnsyncronizableChanges) {
            if (propertyName  == QLatin1String("id")) {
                unsyncronizableElementName = propertyName;
                unsyncronizableChanges
                        = QmlLiveTextPreview::AttributeChangeWarning;
                unsyncronizableChangeLine
                        = parentDefinition->firstSourceLocation().startLine;
                unsyncronizableChangeColumn
                        = parentDefinition->firstSourceLocation().startColumn;
            }
        }

        QVariant expr = scriptCode;
        const bool isLiteral = isLiteralValue(scriptBinding);
        if (isLiteral)
            expr = castToLiteral(scriptCode, scriptBinding);
        appliedChangesToViewer = true;
218
219
220
221
222
        if (m_inspectorAdapter->engineClient())
            m_inspectorAdapter->engineClient()->setBindingForObject(
                        debugId, propertyName, expr,
                        isLiteral, document()->fileName(),
                        scriptBinding->firstSourceLocation().startLine);
223
224
225
226
227
    }

    virtual void resetBindingForObject(int debugId, const QString &propertyName)
    {
        appliedChangesToViewer = true;
228
229
        if (m_inspectorAdapter->engineClient())
            m_inspectorAdapter->engineClient()->resetBindingForObject(debugId, propertyName);
230
231
232
233
234
    }

    virtual void removeObject(int debugId)
    {
        appliedChangesToViewer = true;
235
236
        if (m_inspectorAdapter->toolsClient())
            m_inspectorAdapter->toolsClient()->destroyQmlObject(debugId);
237
238
239
240
241
242
243
244
245
    }

    virtual void createObject(const QString &qmlText, DebugId ref,
                              const QStringList &importList,
                              const QString &filename,
                              int order)
    {
        appliedChangesToViewer = true;
        referenceRefreshRequired = true;
246
247
        if (m_inspectorAdapter->toolsClient())
            m_inspectorAdapter->toolsClient()->createQmlObject(qmlText, ref, importList, filename, order);
248
249
250
251
252
    }

    virtual void reparentObject(int debugId, int newParent)
    {
        appliedChangesToViewer = true;
253
254
        if (m_inspectorAdapter->toolsClient())
            m_inspectorAdapter->toolsClient()->reparentQmlObject(debugId, newParent);
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
    }

    void notifyUnsyncronizableElementChange(UiObjectMember *parent)
    {
        if (unsyncronizableChanges == QmlLiveTextPreview::NoUnsyncronizableChanges) {
            UiObjectDefinition *parentDefinition = cast<UiObjectDefinition *>(parent);
            if (parentDefinition && parentDefinition->qualifiedTypeNameId
                    && !parentDefinition->qualifiedTypeNameId->name.isEmpty())
            {
                unsyncronizableElementName
                        = parentDefinition->qualifiedTypeNameId->name.toString();
                unsyncronizableChanges
                        = QmlLiveTextPreview::ElementChangeWarning;
                unsyncronizableChangeLine
                        = parentDefinition->firstSourceLocation().startLine;
                unsyncronizableChangeColumn
                        = parentDefinition->firstSourceLocation().startColumn;
            }
        }
    }

public:
    UpdateInspector(QmlInspectorAdapter *inspectorAdapter)
        : appliedChangesToViewer(false)
        , referenceRefreshRequired(false)
        , unsyncronizableChanges(QmlLiveTextPreview::NoUnsyncronizableChanges)
        , m_inspectorAdapter(inspectorAdapter)
    {
    }

    bool appliedChangesToViewer;
    bool referenceRefreshRequired;
    QString unsyncronizableElementName;
    QmlLiveTextPreview::UnsyncronizableChangeType unsyncronizableChanges;
    unsigned unsyncronizableChangeLine;
    unsigned unsyncronizableChangeColumn;
    QmlInspectorAdapter *m_inspectorAdapter;

};

Kai Koehne's avatar
Kai Koehne committed
295
bool MapObjectWithDebugReference::visit(UiObjectDefinition *ast)
296
297
298
299
300
301
{
    if (lookupObjects.contains(ast))
        activated++;
    return true;
}

Kai Koehne's avatar
Kai Koehne committed
302
bool MapObjectWithDebugReference::visit(UiObjectBinding *ast)
303
304
305
306
307
308
{
    if (lookupObjects.contains(ast))
        activated++;
    return true;
}

Kai Koehne's avatar
Kai Koehne committed
309
void MapObjectWithDebugReference::endVisit(UiObjectDefinition *ast)
310
{
311
    process(ast);
312
313
    if (lookupObjects.contains(ast))
        activated--;
314
}
315

Kai Koehne's avatar
Kai Koehne committed
316
void MapObjectWithDebugReference::endVisit(UiObjectBinding *ast)
317
{
318
    process(ast);
319
320
    if (lookupObjects.contains(ast))
        activated--;
321
322
}

Kai Koehne's avatar
Kai Koehne committed
323
void MapObjectWithDebugReference::process(UiObjectMember *ast)
324
{
325
326
    if (lookupObjects.isEmpty() || activated) {
        SourceLocation loc = ast->firstSourceLocation();
Kai Koehne's avatar
Kai Koehne committed
327
328
        QHash<QPair<int, int>, DebugIdList>::const_iterator it
                = ids.constFind(qMakePair<int, int>(loc.startLine, loc.startColumn));
329
330
        if (it != ids.constEnd())
            result[ast].append(*it);
331
332
333
    }
}

Kai Koehne's avatar
Kai Koehne committed
334
void MapObjectWithDebugReference::process(UiObjectBinding *ast)
335
336
337
{
    if (lookupObjects.isEmpty() || activated) {
        SourceLocation loc = ast->qualifiedTypeNameId->identifierToken;
Kai Koehne's avatar
Kai Koehne committed
338
339
        QHash<QPair<int, int>, DebugIdList>::const_iterator it
                = ids.constFind(qMakePair<int, int>(loc.startLine, loc.startColumn));
340
341
342
343
344
        if (it != ids.constEnd())
            result[ast].append(*it);
    }
}

345
346
347
348
349
350
351
352
353
354
355
356
357
358
/*!
 * Manages a Qml/JS document for the inspector
 */
QmlLiveTextPreview::QmlLiveTextPreview(const QmlJS::Document::Ptr &doc,
                                       const QmlJS::Document::Ptr &initDoc,
                                       QmlInspectorAdapter *inspectorAdapter,
                                       QObject *parent)
    : QObject(parent)
    , m_previousDoc(doc)
    , m_initialDoc(initDoc)
    , m_applyChangesToQmlInspector(true)
    , m_inspectorAdapter(inspectorAdapter)
    , m_nodeForOffset(0)
    , m_updateNodeForOffset(false)
359
    , m_changesUnsynchronizable(false)
360
    , m_contentsChanged(false)
361
362
363
364
365
{
    QTC_CHECK(doc->fileName() == initDoc->fileName());

    QmlJS::ModelManagerInterface *modelManager
            = QmlJS::ModelManagerInterface::instance();
366
367
368
369
    if (modelManager) {
        connect(modelManager, SIGNAL(documentChangedOnDisk(QmlJS::Document::Ptr)),
                SLOT(documentChanged(QmlJS::Document::Ptr)));
    }
370
371
    connect(m_inspectorAdapter->agent(), SIGNAL(objectTreeUpdated()),
            SLOT(updateDebugIds()));
372
373
374
375
    connect(this,
            SIGNAL(fetchObjectsForLocation(QString,int,int)),
            m_inspectorAdapter->agent(),
            SLOT(fetchContextObjectsForLocation(QString,int,int)));
376
377
    connect(m_inspectorAdapter->agent(), SIGNAL(automaticUpdateFailed()),
            SLOT(onAutomaticUpdateFailed()));
378
379
}

380
381
382
383
384
QmlLiveTextPreview::~QmlLiveTextPreview()
{
    removeOutofSyncInfo();
}

385
void QmlLiveTextPreview::associateEditor(Core::IEditor *editor)
386
{
387
    QTC_ASSERT(editor, return);
388
    using namespace TextEditor;
389
    if (editor->document()->id() == QmlJSEditor::Constants::C_QMLJSEDITOR_ID) {
390
        QTC_ASSERT(QLatin1String(editor->widget()->metaObject()->className()) ==
Nikita Baryshnikov's avatar
Nikita Baryshnikov committed
391
                   QLatin1String("QmlJSEditor::Internal::QmlJSEditorWidget"),
392
393
                   return);

Kai Koehne's avatar
Kai Koehne committed
394
395
        BaseTextEditorWidget *editWidget
                = qobject_cast<BaseTextEditorWidget*>(editor->widget());
396
397
398
399
        QTC_ASSERT(editWidget, return);

        if (!m_editors.contains(editWidget)) {
            m_editors << editWidget;
400
            if (m_inspectorAdapter) {
401
                connect(editWidget, SIGNAL(textChanged()), SLOT(editorContentsChanged()));
Kai Koehne's avatar
Kai Koehne committed
402
                connect(editWidget,
403
404
                        SIGNAL(selectedElementsChanged(QList<QmlJS::AST::UiObjectMember*>,QString)),
                        SLOT(changeSelectedElements(QList<QmlJS::AST::UiObjectMember*>,QString)));
405
            }
406
407
        }
    }
408
409
}

410
void QmlLiveTextPreview::unassociateEditor(Core::IEditor *oldEditor)
411
{
412
    using namespace TextEditor;
413
    if (oldEditor && oldEditor->document()->id()
Kai Koehne's avatar
Kai Koehne committed
414
415
416
            == QmlJSEditor::Constants::C_QMLJSEDITOR_ID) {
        BaseTextEditorWidget *editWidget
                = qobject_cast<BaseTextEditorWidget*>(oldEditor->widget());
417
418
419
420
421
        QTC_ASSERT(editWidget, return);

        if (m_editors.contains(editWidget)) {
            m_editors.removeOne(editWidget);
            disconnect(editWidget, 0, this, 0);
422
423
424
        }
    }
}
425

426
void QmlLiveTextPreview::resetInitialDoc(const QmlJS::Document::Ptr &doc)
427
428
429
430
431
{
    m_initialDoc = doc;
    m_previousDoc = doc;
    m_createdObjects.clear();
    m_debugIds.clear();
432
    m_docWithUnappliedChanges.clear();
433
434
435
436
437
438
439
    m_changesUnsynchronizable = false;
    removeOutofSyncInfo();
}

const QString QmlLiveTextPreview::fileName()
{
    return m_previousDoc->fileName();
440
441
}

442
void QmlLiveTextPreview::setApplyChangesToQmlInspector(bool applyChanges)
443
{
444
445
446
447
    if (applyChanges && !m_applyChangesToQmlInspector) {
        if (m_docWithUnappliedChanges) {
            m_applyChangesToQmlInspector = true;
            documentChanged(m_docWithUnappliedChanges);
448
449
        }
    }
450

451
    m_applyChangesToQmlInspector = applyChanges;
452
453
}

454
void QmlLiveTextPreview::updateDebugIds()
455
{
456
457
458
    if (!m_initialDoc->qmlProgram())
        return;

Kai Koehne's avatar
Kai Koehne committed
459
    DebugIdHash::const_iterator it
460
            = m_inspectorAdapter->agent()->debugIdHash().constFind(
Kai Koehne's avatar
Kai Koehne committed
461
                qMakePair<QString, int>(m_initialDoc->fileName(), 0));
462
    if (it != m_inspectorAdapter->agent()->debugIdHash().constEnd()) {
Kai Koehne's avatar
Kai Koehne committed
463
464
        // Map all the object that comes from the document as it has been loaded
        // by the server.
465
        const QmlJS::Document::Ptr &doc = m_initialDoc;
466

467
        MapObjectWithDebugReference visitor;
468
        visitor.ids = (*it);
469
470
        visitor.filename = doc->fileName();
        doc->qmlProgram()->accept(&visitor);
471

472
        m_debugIds = visitor.result;
473
474
475
476
        if (doc != m_previousDoc) {
            Delta delta;
            m_debugIds = delta(doc, m_previousDoc, m_debugIds);
        }
477
478
479
    }

    const QmlJS::Document::Ptr &doc = m_previousDoc;
480
481
    if (!doc->qmlProgram())
        return;
482

483
    // Map the root nodes of the document.
484
    if (doc->qmlProgram()->members &&  doc->qmlProgram()->members->member) {
Kai Koehne's avatar
Kai Koehne committed
485
        UiObjectMember *root = doc->qmlProgram()->members->member;
486
        QHashIterator<int,QString> rIds(m_inspectorAdapter->agent()->rootObjectIds());
487
        QList<int> r;
488
489
490
491
492
        while (rIds.hasNext()) {
            rIds.next();
            if (rIds.value() == doc->componentName())
                r += rIds.key();
            }
493
494
495
        if (!r.isEmpty())
            m_debugIds[root] += r;
    }
496
497

    // Map the node of the later created objects.
Kai Koehne's avatar
Kai Koehne committed
498
    for (QHash<Document::Ptr,QSet<UiObjectMember*> >::const_iterator it
499
500
         = m_createdObjects.constBegin();
         it != m_createdObjects.constEnd(); ++it) {
501
502

        const QmlJS::Document::Ptr &doc = it.key();
503

504
        DebugIdHash::const_iterator id_it = m_inspectorAdapter->agent()->debugIdHash().constFind(
505
                    qMakePair<QString, int>(doc->fileName(), doc->editorRevision()));
506
        if (id_it == m_inspectorAdapter->agent()->debugIdHash().constEnd())
507
508
            continue;

509
        MapObjectWithDebugReference visitor;
510
        visitor.ids = *id_it;
511
512
513
514
515
        visitor.filename = doc->fileName();
        visitor.lookupObjects = it.value();
        doc->qmlProgram()->accept(&visitor);

        Delta::DebugIdMap debugIds = visitor.result;
516
517
518
519
        if (doc != m_previousDoc) {
            Delta delta;
            debugIds = delta(doc, m_previousDoc, debugIds);
        }
520
521
        for (Delta::DebugIdMap::const_iterator it2 = debugIds.constBegin();
             it2 != debugIds.constEnd(); ++it2) {
522
523
524
            m_debugIds[it2.key()] += it2.value();
        }
    }
525
526
    if (m_updateNodeForOffset)
        changeSelectedElements(m_lastOffsets, QString());
527
528
}

529
void QmlLiveTextPreview::changeSelectedElements(const QList<QmlJS::AST::UiObjectMember*> offsetObjects,
530
531
532
533
                                                const QString &wordAtCursor)
{
    if (m_editors.isEmpty() || !m_previousDoc)
        return;
534

535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
    QList<int> offsets;
    foreach (QmlJS::AST::UiObjectMember *member, offsetObjects)
        offsets << member->firstSourceLocation().offset;

    if (!changeSelectedElements(offsets, wordAtCursor) && m_initialDoc && offsetObjects.count()) {
        m_updateNodeForOffset = true;
        emit fetchObjectsForLocation(m_initialDoc->fileName(),
                                     offsetObjects.first()->firstSourceLocation().startLine,
                                     offsetObjects.first()->firstSourceLocation().startColumn);
    }
}

bool QmlLiveTextPreview::changeSelectedElements(const QList<int> offsets,
                                                const QString &wordAtCursor)
{
550
551
    m_updateNodeForOffset = false;
    m_lastOffsets = offsets;
Kai Koehne's avatar
Kai Koehne committed
552
    ObjectReference objectRefUnderCursor;
553
    objectRefUnderCursor
554
            = m_inspectorAdapter->agent()->objectForName(wordAtCursor);
555

556
557
    QList<int> selectedReferences;
    bool containsReferenceUnderCursor = false;
558

559
560
561
    foreach (int offset, offsets) {
        if (offset >= 0) {
            QList<int> list = objectReferencesForOffset(offset);
562

563
            if (!containsReferenceUnderCursor
564
                    && objectRefUnderCursor.isValid()) {
565
566
567
568
569
570
                foreach (int id, list) {
                    if (id == objectRefUnderCursor.debugId()) {
                        containsReferenceUnderCursor = true;
                        break;
                    }
                }
571
            }
572

573
            selectedReferences << list;
574
575
576
        }
    }

577
578
579
    // fallback: use ref under cursor if nothing else is found
    if (selectedReferences.isEmpty()
            && !containsReferenceUnderCursor
580
            && objectRefUnderCursor.isValid()) {
581
        selectedReferences << objectRefUnderCursor.debugId();
Lasse Holmstedt's avatar
Lasse Holmstedt committed
582
    }
583

584
585
586
587
    if (selectedReferences.isEmpty())
        return false;
    emit selectedItemsChanged(selectedReferences);
    return true;
588
}
589

590
void QmlLiveTextPreview::documentChanged(QmlJS::Document::Ptr doc)
591
{
592
    if (doc->fileName() != m_previousDoc->fileName())
593
594
        return;

595
596
597
598
599
600
    // Changes to be applied when changes were made from the editor.
    // m_contentsChanged ensures that the changes were made by the user in
    // the editor before starting with the comparisons.
    if (!m_contentsChanged)
        return;

601
    if (m_applyChangesToQmlInspector) {
602
603
        m_docWithUnappliedChanges.clear();

604
        if (doc && m_previousDoc && doc->fileName() == m_previousDoc->fileName()) {
605
            if (doc->fileName().endsWith(QLatin1String(".js"))) {
606
                showSyncWarning(JSChangeWarning, QString(), 0, 0);
607
608
609
610
611
612
613
614
                m_previousDoc = doc;
                return;
            }
            if (doc->qmlProgram() && m_previousDoc->qmlProgram()) {
                UpdateInspector delta(m_inspectorAdapter);
                m_debugIds = delta(m_previousDoc, doc, m_debugIds);

                if (delta.referenceRefreshRequired)
615
                    m_inspectorAdapter->agent()->queryEngineContext();
616
617
618
619
620
621
622
623
624
625
626
627
628


                if (delta.unsyncronizableChanges != NoUnsyncronizableChanges) {
                    showSyncWarning(delta.unsyncronizableChanges,
                                    delta.unsyncronizableElementName,
                                    delta.unsyncronizableChangeLine,
                                    delta.unsyncronizableChangeColumn);
                    m_previousDoc = doc;
                    return;
                }
                m_previousDoc = doc;
                if (!delta.newObjects.isEmpty())
                    m_createdObjects[doc] += delta.newObjects;
629
630
                if (m_inspectorAdapter->toolsClient())
                    m_inspectorAdapter->toolsClient()->clearComponentCache();
631
            }
632
633
634
        }
    } else {
        m_docWithUnappliedChanges = doc;
635
    }
636
637
638
639
640
641
    m_contentsChanged = false;
}

void QmlLiveTextPreview::editorContentsChanged()
{
    m_contentsChanged = true;
642
643
}

644
645
void QmlLiveTextPreview::onAutomaticUpdateFailed()
{
646
    showSyncWarning(AutomaticUpdateFailed, QString(), UINT_MAX, UINT_MAX);
647
648
}

649
QList<int> QmlLiveTextPreview::objectReferencesForOffset(quint32 offset)
650
{
651
652
653
654
655
656
657
658
659
660
661
662
663
664
    QList<int> result;
    QHashIterator<QmlJS::AST::UiObjectMember*, QList<int> > iter(m_debugIds);
    QmlJS::AST::UiObjectMember *possibleNode = 0;
    while (iter.hasNext()) {
        iter.next();
        QmlJS::AST::UiObjectMember *member = iter.key();
        quint32 startOffset = member->firstSourceLocation().offset;
        quint32 endOffset = member->lastSourceLocation().offset;
        if (startOffset <= offset && offset <= endOffset) {
            if (!possibleNode)
                possibleNode = member;
            if (possibleNode->firstSourceLocation().offset <= startOffset &&
                    endOffset <= possibleNode->lastSourceLocation().offset)
                possibleNode = member;
665
        }
666
667
668
669
670
671
672
673
674
675
676
    }
    if (possibleNode) {
        if (possibleNode != m_nodeForOffset) {
            //We have found a better match, set flag so that we can
            //query again to check if this is the best match for the offset
            m_updateNodeForOffset = true;
            m_nodeForOffset = possibleNode;
        }
        result = m_debugIds.value(possibleNode);
    }
    return result;
677
678
}

679
void QmlLiveTextPreview::showSyncWarning(
Kai Koehne's avatar
Kai Koehne committed
680
681
        UnsyncronizableChangeType unsyncronizableChangeType,
        const QString &elementName, unsigned line, unsigned column)
682
{
683
684
    QString errorMessage;
    switch (unsyncronizableChangeType) {
685
    case AttributeChangeWarning:
Kai Koehne's avatar
Kai Koehne committed
686
687
        errorMessage = tr("The %1 attribute at line %2, column %3 cannot be "
                          "changed without reloading the QML application. ")
688
689
690
                .arg(elementName, QString::number(line), QString::number(column));
        break;
    case ElementChangeWarning:
Kai Koehne's avatar
Kai Koehne committed
691
692
        errorMessage = tr("The %1 element at line %2, column %3 cannot be "
                          "changed without reloading the QML application. ")
693
694
                .arg(elementName, QString::number(line), QString::number(column));
        break;
695
    case JSChangeWarning:
696
        errorMessage = tr("The changes in JavaScript cannot be applied "
697
698
                          "without reloading the QML application. ");
        break;
699
700
701
702
    case AutomaticUpdateFailed:
        errorMessage = tr("The changes made cannot be applied without "
                          "reloading the QML application. ");
        break;
703
    case QmlLiveTextPreview::NoUnsyncronizableChanges:
704
705
    default:
        return;
706
707
    }

708
    m_changesUnsynchronizable = true;
709
710
    errorMessage.append(tr("You can continue debugging, but behavior can be unexpected."));

711
712
713
714
715
716
    // Clear infobars if present before showing the same. Otherwise multiple infobars
    // will be shown in case the user changes and saves the file multiple times.
    removeOutofSyncInfo();

    foreach (TextEditor::BaseTextEditorWidget *editor, m_editors) {
        if (editor) {
717
            Core::InfoBar *infoBar = editor->textDocument()->infoBar();
Orgad Shaneh's avatar
Orgad Shaneh committed
718
            Core::InfoBarEntry info(Core::Id(INFO_OUT_OF_SYNC), errorMessage);
719
720
            BaseToolsClient *toolsClient = m_inspectorAdapter->toolsClient();
            if (toolsClient && toolsClient->supportReload())
721
722
                info.setCustomButtonInfo(tr("Reload QML"), this,
                                         SLOT(reloadQml()));
723
724
725
726
727
728
729
730
731
732
733
734
735
            infoBar->addInfo(info);
        }
    }
}

void QmlLiveTextPreview::reloadQml()
{
    removeOutofSyncInfo();
    emit reloadRequest();
}

void QmlLiveTextPreview::removeOutofSyncInfo()
{
736
    foreach (TextEditor::BaseTextEditorWidget *editor, m_editors) {
Kai Koehne's avatar
Kai Koehne committed
737
        if (editor) {
738
            Core::InfoBar *infoBar = editor->textDocument()->infoBar();
Orgad Shaneh's avatar
Orgad Shaneh committed
739
            infoBar->removeInfo(Core::Id(INFO_OUT_OF_SYNC));
Kai Koehne's avatar
Kai Koehne committed
740
741
        }
    }
742
743
}

744
} // namespace Internal
745
} // namespace Debugger