qmllivetextpreview.cpp 26.2 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
    using namespace TextEditor;
388
    if (editor->id() == QmlJSEditor::Constants::C_QMLJSEDITOR_ID) {
389
390
391
392
        QTC_ASSERT(QLatin1String(editor->widget()->metaObject()->className()) ==
                   QLatin1String("QmlJSEditor::QmlJSTextEditorWidget"),
                   return);

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

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

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

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

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

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

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

450
    m_applyChangesToQmlInspector = applyChanges;
451
452
}

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

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

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

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

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

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

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

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

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

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

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

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

534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
    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)
{
549
550
    m_updateNodeForOffset = false;
    m_lastOffsets = offsets;
Kai Koehne's avatar
Kai Koehne committed
551
    ObjectReference objectRefUnderCursor;
552
    objectRefUnderCursor
553
            = m_inspectorAdapter->agent()->objectForName(wordAtCursor);
554

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

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

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

572
            selectedReferences << list;
573
574
575
        }
    }

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

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

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

594
595
596
597
598
599
    // 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;

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

603
        if (doc && m_previousDoc && doc->fileName() == m_previousDoc->fileName()) {
604
            if (doc->fileName().endsWith(QLatin1String(".js"))) {
605
                showSyncWarning(JSChangeWarning, QString(), 0, 0);
606
607
608
609
610
611
612
613
                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)
614
                    m_inspectorAdapter->agent()->queryEngineContext();
615
616
617
618
619
620
621
622
623
624
625
626
627


                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;
628
629
                if (m_inspectorAdapter->toolsClient())
                    m_inspectorAdapter->toolsClient()->clearComponentCache();
630
            }
631
632
633
        }
    } else {
        m_docWithUnappliedChanges = doc;
634
    }
635
636
637
638
639
640
    m_contentsChanged = false;
}

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

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

648
QList<int> QmlLiveTextPreview::objectReferencesForOffset(quint32 offset)
649
{
650
651
652
653
654
655
656
657
658
659
660
661
662
663
    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;
664
        }
665
666
667
668
669
670
671
672
673
674
675
    }
    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;
676
677
}

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

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

710
711
712
713
714
715
    // 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) {
716
            Core::InfoBar *infoBar = editor->baseTextDocument()->infoBar();
Orgad Shaneh's avatar
Orgad Shaneh committed
717
            Core::InfoBarEntry info(Core::Id(INFO_OUT_OF_SYNC), errorMessage);
718
719
            BaseToolsClient *toolsClient = m_inspectorAdapter->toolsClient();
            if (toolsClient && toolsClient->supportReload())
720
721
                info.setCustomButtonInfo(tr("Reload QML"), this,
                                         SLOT(reloadQml()));
722
723
724
725
726
727
728
729
730
731
732
733
734
            infoBar->addInfo(info);
        }
    }
}

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

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

743
} // namespace Internal
744
} // namespace Debugger