subcomponentmanager.cpp 16.1 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
2
**
3
4
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
7
**
hjk's avatar
hjk committed
8
9
10
11
** 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
12
13
14
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
15
**
16
17
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
18
19
20
21
22
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
con's avatar
con committed
23
**
hjk's avatar
hjk committed
24
****************************************************************************/
25

26
27
#include "subcomponentmanager.h"

28
29
#include <qmldesignerconstants.h>

30
#include "model.h"
31
#include "metainforeader.h"
32

33
#include <utils/algorithm.h>
34
#include <utils/hostosinfo.h>
35
#include <coreplugin/messagebox.h>
36

37
#include <QDir>
38
#include <QMessageBox>
39
40
#include <QUrl>

41
42
#include <qmljs/qmljslink.h>
#include <qmljs/parser/qmljsast_p.h>
43
#include <qmljs/parser/qmljsengine_p.h>
44
45
#include <qmljs/qmljsmodelmanagerinterface.h>

46
47
enum { debug = false };

48
QT_BEGIN_NAMESPACE
49

50
// Allow usage of QFileInfo in Utils::sort
51
52
53
54
55
56
57

static bool operator<(const QFileInfo &file1, const QFileInfo &file2)
{
    return file1.filePath() < file2.filePath();
}


58
QT_END_NAMESPACE
59

60

61
62
static inline bool checkIfDerivedFromItem(const QString &fileName)
{
63
64
    return true;

65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
    QmlJS::Snapshot snapshot;


    QmlJS::ModelManagerInterface *modelManager = QmlJS::ModelManagerInterface::instance();
    if (modelManager)
        snapshot =  modelManager->snapshot();

    QFile file(fileName);
    if (!file.open(QIODevice::ReadOnly))
        return false;

    QByteArray source = file.readAll();
    file.close();

    QmlJS::Document::MutablePtr document =
            QmlJS::Document::create(fileName.isEmpty() ?
81
                                        QStringLiteral("<internal>") : fileName, QmlJS::Dialect::Qml);
82
    document->setSource(QString::fromUtf8(source));
83
84
85
    document->parseQml();


86
    if (!document->isParsedCorrectly())
87
88
89
90
        return false;

    snapshot.insert(document);

91
    QmlJS::Link link(snapshot, modelManager->defaultVContext(document->language(), document), QmlJS::ModelManagerInterface::instance()->builtins(document));
92
93
94
95
96
97
98
99
100
101
102
103
104
105

    QList<QmlJS::DiagnosticMessage> diagnosticLinkMessages;
    QmlJS::ContextPtr context = link(document, &diagnosticLinkMessages);

    QmlJS::AST::UiObjectMember *astRootNode = 0;
    if (QmlJS::AST::UiProgram *program = document->qmlProgram())
        if (program->members)
            astRootNode = program->members->member;

    QmlJS::AST::UiObjectDefinition *definition = QmlJS::AST::cast<QmlJS::AST::UiObjectDefinition *>(astRootNode);

    if (!definition)
        return false;

106
    const QmlJS::ObjectValue *objectValue = context->lookupType(document.data(), definition->qualifiedTypeNameId);
107
108
109
110

    QList<const QmlJS::ObjectValue *> prototypes = QmlJS::PrototypeIterator(objectValue, context).all();

    foreach (const QmlJS::ObjectValue *prototype, prototypes) {
111
        if (prototype->className() == QLatin1String("Item"))
112
113
114
115
116
117
            return true;
    }

    return false;
}

118
namespace QmlDesigner {
119
static const QString s_qmlFilePattern = QStringLiteral("*.qml");
120

121
122
123
SubComponentManager::SubComponentManager(Model *model, QObject *parent)
    : QObject(parent),
      m_model(model)
124
125
126
127
{
    connect(&m_watcher, SIGNAL(directoryChanged(QString)), this, SLOT(parseDirectory(QString)));
}

128
void SubComponentManager::addImport(int pos, const Import &import)
129
130
{
    if (debug)
131
        qDebug() << Q_FUNC_INFO << pos << import.file().toUtf8();
132

133
134
    if (import.isFileImport()) {
        QFileInfo dirInfo = QFileInfo(m_filePath.resolved(import.file()).toLocalFile());
135
        if (dirInfo.exists() && dirInfo.isDir()) {
136
137
            const QString canonicalDirPath = dirInfo.canonicalFilePath();
            m_watcher.addPath(canonicalDirPath);
138
            //m_dirToQualifier.insertMulti(canonicalDirPath, import.qualifier()); ### todo: proper support for import as
139
140
        }
    } else {
141
        QString url = import.url();
Orgad Shaneh's avatar
Orgad Shaneh committed
142

143
144
        url.replace(QLatin1Char('.'), QLatin1Char('/'));

Friedemann Kleint's avatar
Friedemann Kleint committed
145
146
        foreach (const QString &path, importPaths()) {
            url  = path + QLatin1Char('/') + url;
147
148
149
150
            QFileInfo dirInfo = QFileInfo(url);
            if (dirInfo.exists() && dirInfo.isDir()) {
                const QString canonicalDirPath = dirInfo.canonicalFilePath();
                m_watcher.addPath(canonicalDirPath);
151
                //m_dirToQualifier.insertMulti(canonicalDirPath, import.qualifier()); ### todo: proper support for import as
152
153
            }
        }
154
        // TODO: QDeclarativeDomImport::Library
155
156
157
158
159
    }

    m_imports.insert(pos, import);
}

160
void SubComponentManager::removeImport(int pos)
161
{
162
    const Import import = m_imports.takeAt(pos);
163

164
165
    if (import.isFileImport()) {
        const QFileInfo dirInfo = QFileInfo(m_filePath.resolved(import.file()).toLocalFile());
166
        const QString canonicalDirPath = dirInfo.canonicalFilePath();
167

168
        //m_dirToQualifier.remove(canonicalDirPath, import.qualifier()); ### todo: proper support for import as
169

170
171
        if (!m_dirToQualifier.contains(canonicalDirPath))
            m_watcher.removePath(canonicalDirPath);
172

173
174
175
176
//        foreach (const QFileInfo &monitoredFile, watchedFiles(canonicalDirPath)) { ### todo: proper support for import as
//            if (!m_dirToQualifier.contains(canonicalDirPath))
//                unregisterQmlFile(monitoredFile, import.qualifier());
//        }
177
    } else {
178
            // TODO: QDeclarativeDomImport::Library
179
180
181
    }
}

182
void SubComponentManager::parseDirectories()
183
184
185
186
187
{
    if (!m_filePath.isEmpty()) {
        const QString file = m_filePath.toLocalFile();
        QFileInfo dirInfo = QFileInfo(QFileInfo(file).path());
        if (dirInfo.exists() && dirInfo.isDir())
188
            parseDirectory(dirInfo.canonicalFilePath());
189

190
        foreach (const QString &subDir, QDir(QFileInfo(file).path()).entryList(QDir::Dirs | QDir::NoDot | QDir::NoDotDot)) {
191
            parseDirectory(dirInfo.canonicalFilePath() + QLatin1String("/") + subDir, true, subDir.toUtf8());
192
        }
193
194
    }

195
196
197
    foreach (const Import &import, m_imports) {
        if (import.isFileImport()) {
            QFileInfo dirInfo = QFileInfo(m_filePath.resolved(import.file()).toLocalFile());
198
            if (dirInfo.exists() && dirInfo.isDir())
199
                parseDirectory(dirInfo.canonicalFilePath(), true, dirInfo.baseName().toUtf8());
200
        } else {
201
            QString url = import.url();
202
203
            url.replace(QLatin1Char('.'), QLatin1Char('/'));
            QFileInfo dirInfo = QFileInfo(url);
Friedemann Kleint's avatar
Friedemann Kleint committed
204
            foreach (const QString &path, importPaths()) {
205
206
                QString fullUrl  = path + QLatin1Char('/') + url;
                dirInfo = QFileInfo(fullUrl);
207
208
209
210
211
212
213
214
215

                if (dirInfo.exists() && dirInfo.isDir()) {
                    //### todo full qualified names QString nameSpace = import.uri();
                    parseDirectory(dirInfo.canonicalFilePath(), false);
                }

                QString fullUrlVersion = path + QLatin1Char('/') + url + QLatin1Char('.') + import.version().split(".").first();
                dirInfo = QFileInfo(fullUrlVersion);

216
217
218
219
220
                if (dirInfo.exists() && dirInfo.isDir()) {
                    //### todo full qualified names QString nameSpace = import.uri();
                    parseDirectory(dirInfo.canonicalFilePath(), false);
                }
            }
221
222
223
224
        }
    }
}

225
void SubComponentManager::parseDirectory(const QString &canonicalDirPath, bool addToLibrary, const TypeName& qualification)
226
{
227
    if (!model() || !model()->rewriterView())
228
        return;
229

230
    QDir designerDir(canonicalDirPath + QLatin1String(Constants::QML_DESIGNER_SUBFOLDER));
231
232
    if (designerDir.exists()) {
        QStringList filter;
233
        filter << QLatin1String("*.metainfo");
234
235
236
237
238
239
        designerDir.setNameFilters(filter);

        QStringList metaFiles = designerDir.entryList(QDir::Files);
        foreach (const QFileInfo &metaInfoFile, designerDir.entryInfoList(QDir::Files)) {
            if (model() && model()->metaInfo().itemLibraryInfo()) {
                Internal::MetaInfoReader reader(model()->metaInfo());
240
                reader.setQualifcation(qualification);
241
242
                try {
                    reader.readMetaInfoFile(metaInfoFile.absoluteFilePath(), true);
243
                } catch (const InvalidMetaInfoException &e) {
244
                    qWarning() << e.description();
hjk's avatar
hjk committed
245
                    const QString errorMessage = metaInfoFile.absoluteFilePath() + QLatin1Char('\n') + QLatin1Char('\n') + reader.errors().join(QLatin1Char('\n'));
246
                    Core::AsynchronousMessageBox::warning(QCoreApplication::translate("SubComponentManager::parseDirectory", "Invalid meta info"),
247
                                                           errorMessage);
248
249
250
                }
            }
        }
251
        if (!metaFiles.isEmpty())
252
253
254
            return;
    }

255
    if (debug)
256
        qDebug() << Q_FUNC_INFO << canonicalDirPath;
257

258
    QDir dir(canonicalDirPath);
259

260
    dir.setNameFilters(QStringList(s_qmlFilePattern));
261
262
    dir.setFilter(QDir::Files | QDir::Readable | QDir::CaseSensitive);

263
    QList<QFileInfo> monitoredList = watchedFiles(canonicalDirPath);
264
265
266
267
268
269
270
    QList<QFileInfo> newList;
    foreach (const QFileInfo &qmlFile, dir.entryInfoList()) {
        if (QFileInfo(m_filePath.toLocalFile()) == qmlFile) {
            // do not parse main file
            continue;
        }
        if (!qmlFile.fileName().at(0).isUpper()) {
271
            // QML sub components must be upper case
272
273
274
275
276
            continue;
        }
        newList << qmlFile;
    }

277
278
    Utils::sort(monitoredList);
    Utils::sort(newList);
279
280
281

    if (debug)
        qDebug() << "monitored list " << monitoredList.size() << "new list " << newList.size();
282
283
    auto oldIter = monitoredList.constBegin();
    auto newIter = newList.constBegin();
284
285
286
287
288
289
290
291
292
293

    while (oldIter != monitoredList.constEnd() && newIter != newList.constEnd()) {
        const QFileInfo oldFileInfo = *oldIter;
        const QFileInfo newFileInfo = *newIter;
        if (oldFileInfo == newFileInfo) {
            ++oldIter;
            ++newIter;
            continue;
        }
        if (oldFileInfo < newFileInfo) {
294
            foreach (const QString &qualifier, m_dirToQualifier.value(canonicalDirPath))
295
296
297
298
299
300
                unregisterQmlFile(oldFileInfo, qualifier);
            m_watcher.removePath(oldFileInfo.filePath());
            ++oldIter;
            continue;
        }
        // oldFileInfo > newFileInfo
301
        parseFile(newFileInfo.filePath(), addToLibrary, QString::fromUtf8(qualification));
302
303
304
305
        ++newIter;
    }

    while (oldIter != monitoredList.constEnd()) {
306
        foreach (const QString &qualifier, m_dirToQualifier.value(canonicalDirPath))
307
308
309
310
311
            unregisterQmlFile(*oldIter, qualifier);
        ++oldIter;
    }

    while (newIter != newList.constEnd()) {
312
        parseFile(newIter->filePath(), addToLibrary, QString::fromUtf8(qualification));
313
        if (debug)
314
            qDebug() << "m_watcher.addPath(" << newIter->filePath() << ')';
315
316
317
318
        ++newIter;
    }
}

319
void SubComponentManager::parseFile(const QString &canonicalFilePath, bool addToLibrary, const QString&  qualification)
320
321
{
    if (debug)
322
        qDebug() << Q_FUNC_INFO << canonicalFilePath;
323

324
    QFile file(canonicalFilePath);
325
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
326
327
        return;

328
    QString dir = QFileInfo(canonicalFilePath).path();
329
    foreach (const QString &qualifier, m_dirToQualifier.values(dir)) {
330
        registerQmlFile(canonicalFilePath, qualifier, addToLibrary);
331
    }
332
    registerQmlFile(canonicalFilePath, qualification, addToLibrary);
333
334
}

335
void SubComponentManager::parseFile(const QString &canonicalFilePath)
336
337
338
339
{
    parseFile(canonicalFilePath, true, QString());
}

340
// dirInfo must already contain a canonical path
341
QList<QFileInfo> SubComponentManager::watchedFiles(const QString &canonicalDirPath)
342
343
344
345
346
{
    QList<QFileInfo> files;

    foreach (const QString &monitoredFile, m_watcher.files()) {
        QFileInfo fileInfo(monitoredFile);
347
        if (fileInfo.dir().absolutePath() == canonicalDirPath)
348
349
350
351
352
            files.append(fileInfo);
    }
    return files;
}

353
void SubComponentManager::unregisterQmlFile(const QFileInfo &fileInfo, const QString &qualifier)
354
355
356
{
    QString componentName = fileInfo.baseName();
    if (!qualifier.isEmpty())
357
        componentName = qualifier + QLatin1Char('.') + componentName;
358
359
}

360

361
void SubComponentManager::registerQmlFile(const QFileInfo &fileInfo, const QString &qualifier,
362
                                                 bool addToLibrary)
363
{
364
365
366
    if (!model())
        return;

367
368
369
    if (!checkIfDerivedFromItem(fileInfo.absoluteFilePath()))
        return;

370
    QString componentName = fileInfo.baseName();
371
372
373
374
375
    const QString baseComponentName = componentName;

    QString fixedQualifier = qualifier;
    if (!qualifier.isEmpty()) {
        fixedQualifier = qualifier;
376
        if (qualifier.right(1) == QStringLiteral("."))
377
            fixedQualifier.chop(1); //remove last char if it is a dot
378
        componentName = fixedQualifier + QLatin1Char('.') + componentName;
379
    }
380
381
382
383

    if (debug)
        qDebug() << "SubComponentManager" << __FUNCTION__ << componentName;

384
385
386
    if (addToLibrary) {
        // Add file components to the library
        ItemLibraryEntry itemLibraryEntry;
387
        itemLibraryEntry.setType(componentName.toUtf8());
388
        itemLibraryEntry.setName(baseComponentName);
389
        itemLibraryEntry.setCategory(QLatin1String("QML Components"));
390
        if (!qualifier.isEmpty()) {
391
            itemLibraryEntry.setRequiredImport(fixedQualifier);
392
        }
393

394

395
        if (!model()->metaInfo().itemLibraryInfo()->containsEntry(itemLibraryEntry)) {
396
397
398

            model()->metaInfo().itemLibraryInfo()->addEntry(itemLibraryEntry);
        }
399
    }
400
401
}

402
Model *SubComponentManager::model() const
403
404
405
406
{
    return m_model.data();
}

407
408
QStringList SubComponentManager::importPaths() const
{
Marco Bubke's avatar
Marco Bubke committed
409
410
    if (model())
        return model()->importPaths();
411
412
413
414

    return QStringList();
}

415
416
417
418
419
420
421
422
423
424

/*!
  \class SubComponentManager

  Detects & monitors (potential) component files in a list of directories, and registers
  these in the metatype system.
*/

QStringList SubComponentManager::directories() const
{
425
    return m_watcher.directories();
426
427
428
429
}

QStringList SubComponentManager::qmlFiles() const
{
430
    return m_watcher.files();
431
432
}

433
void SubComponentManager::update(const QUrl &filePath, const QList<Import> &imports)
434
435
436
437
438
439
{
    if (debug)
        qDebug() << Q_FUNC_INFO << filePath << imports.size();

    QFileInfo oldDir, newDir;

440
441
    if (!m_filePath.isEmpty()) {
        const QString file = m_filePath.toLocalFile();
442
443
444
445
446
447
448
        oldDir = QFileInfo(QFileInfo(file).path());
    }
    if (!filePath.isEmpty()) {
        const QString file = filePath.toLocalFile();
        newDir = QFileInfo(QFileInfo(file).path());
    }

449
    m_filePath = filePath;
450
451
452
453
454
455

    //
    // (implicit) import of local directory
    //
    if (oldDir != newDir) {
        if (!oldDir.filePath().isEmpty()) {
456
457
458
            m_dirToQualifier.remove(oldDir.canonicalFilePath(), QString());
            if (!m_dirToQualifier.contains(oldDir.canonicalFilePath()))
                m_watcher.removePath(oldDir.filePath());
459
460
        }

461
        if (!newDir.filePath().isEmpty())
462
            m_dirToQualifier.insertMulti(newDir.canonicalFilePath(), QString());
463
464
465
466
467
468
469
470
    }

    //
    // Imports
    //

    // skip first list items until the lists differ
    int i = 0;
471
472
    while (i < qMin(imports.size(), m_imports.size())) {
        if (!(imports.at(i) == m_imports.at(i)))
473
474
475
476
            break;
        ++i;
    }

477
478
    for (int ii = m_imports.size() - 1; ii >= i; --ii)
        removeImport(ii);
479
480

    for (int ii = i; ii < imports.size(); ++ii) {
481
        addImport(ii, imports.at(ii));
482
483
    }

484
    parseDirectories();
485
486
487
488
}

} // namespace QmlDesigner