subcomponentmanager.cpp 14.8 KB
Newer Older
1
2
3
4
/**************************************************************************
**
** This file is part of Qt Creator
**
hjk's avatar
hjk committed
5
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** 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.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/

#include "subcomponentmanager.h"
#include "metainfo.h"

#include <QDir>
#include <QMetaType>
#include <QUrl>
36
37
#include <QDeclarativeEngine>
#include <private/qdeclarativemetatype_p.h>
38
#include <QFileSystemWatcher>
39
#include <private/qdeclarativedom_p.h>
40
41
42

enum { debug = false };

43
QT_BEGIN_NAMESPACE
44

45
// Allow usage of QFileInfo in qSort
46
47
48
49
50
51
52

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


53
QT_END_NAMESPACE
54

55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
static inline QStringList importPaths() {
    QStringList paths;

    // env import paths
    QByteArray envImportPath = qgetenv("QML_IMPORT_PATH");
    if (!envImportPath.isEmpty()) {
#if defined(Q_OS_WIN) || defined(Q_OS_SYMBIAN)
        QLatin1Char pathSep(';');
#else
        QLatin1Char pathSep(':');
#endif
        paths = QString::fromLatin1(envImportPath).split(pathSep, QString::SkipEmptyParts);
    }

    return paths;
}

72
73
74
75
76
77
78
79
80
81
82
83
namespace QmlDesigner {

namespace Internal {

static const QString QMLFILEPATTERN = QString(QLatin1String("*.qml"));


class SubComponentManagerPrivate : QObject {
    Q_OBJECT
public:
    SubComponentManagerPrivate(MetaInfo metaInfo, SubComponentManager *q);

84
    void addImport(int pos, const QDeclarativeDomImport &import);
85
86
87
88
    void removeImport(int pos);
    void parseDirectories();

public slots:
89
90
    void parseDirectory(const QString &canonicalDirPath,  bool addToLibrary = true, const QString& qualification = QString());
    void parseFile(const QString &canonicalFilePath,  bool addToLibrary, const QString&);
91
    void parseFile(const QString &canonicalFilePath);
92
93

public:
94
    QList<QFileInfo> watchedFiles(const QString &canonicalDirPath);
95
    void unregisterQmlFile(const QFileInfo &fileInfo, const QString &qualifier);
96
    void registerQmlFile(const QFileInfo &fileInfo, const QString &qualifier, const QDeclarativeDomDocument &document,  bool addToLibrary);
97
98
99
100

    SubComponentManager *m_q;

    MetaInfo m_metaInfo;
101
    QDeclarativeEngine m_engine;
102
103
104

    QFileSystemWatcher m_watcher;

105
106
    // key: canonical directory path
    QMultiHash<QString,QString> m_dirToQualifier;
107
108
109

    QUrl m_filePath;

110
    QList<QDeclarativeDomImport> m_imports;
111
112
113
114
115
116
117
118
119
120
};

SubComponentManagerPrivate::SubComponentManagerPrivate(MetaInfo metaInfo, SubComponentManager *q) :
        m_q(q),
        m_metaInfo(metaInfo)
{
    connect(&m_watcher, SIGNAL(directoryChanged(QString)), this, SLOT(parseDirectory(QString)));
    connect(&m_watcher, SIGNAL(fileChanged(QString)), this, SLOT(parseFile(QString)));
}

121
void SubComponentManagerPrivate::addImport(int pos, const QDeclarativeDomImport &import)
122
123
124
125
{
    if (debug)
        qDebug() << Q_FUNC_INFO << pos << import.uri();

126
    if (import.type() == QDeclarativeDomImport::File) {
127
128
        QFileInfo dirInfo = QFileInfo(m_filePath.resolved(import.uri()).toLocalFile());
        if (dirInfo.exists() && dirInfo.isDir()) {
129
130
131
            const QString canonicalDirPath = dirInfo.canonicalFilePath();
            m_watcher.addPath(canonicalDirPath);
            m_dirToQualifier.insertMulti(canonicalDirPath, import.qualifier());
132
133
        }
    } else {
134
135
136
137
138
139
140
141
142
143
144
145
146
        QString url = import.uri();
        
        url.replace(QLatin1Char('.'), QLatin1Char('/'));

        foreach(const QString path, importPaths()) {
            url  = path + QLatin1String("/") + url;
            QFileInfo dirInfo = QFileInfo(url);
            if (dirInfo.exists() && dirInfo.isDir()) {
                const QString canonicalDirPath = dirInfo.canonicalFilePath();
                m_watcher.addPath(canonicalDirPath);
                m_dirToQualifier.insertMulti(canonicalDirPath, import.qualifier());
            }
        }
147
        // TODO: QDeclarativeDomImport::Library
148
149
150
151
152
153
154
    }

    m_imports.insert(pos, import);
}

void SubComponentManagerPrivate::removeImport(int pos)
{
155
    const QDeclarativeDomImport import = m_imports.takeAt(pos);
156

157
    if (import.type() == QDeclarativeDomImport::File) {
158
159
        const QFileInfo dirInfo = QFileInfo(m_filePath.resolved(import.uri()).toLocalFile());
        const QString canonicalDirPath = dirInfo.canonicalFilePath();
160

161
        m_dirToQualifier.remove(canonicalDirPath, import.qualifier());
162

163
164
        if (!m_dirToQualifier.contains(canonicalDirPath))
            m_watcher.removePath(canonicalDirPath);
165

166
167
        foreach (const QFileInfo &monitoredFile, watchedFiles(canonicalDirPath)) {
            if (!m_dirToQualifier.contains(canonicalDirPath))
168
169
170
171
                m_watcher.removePath(monitoredFile.filePath());
            unregisterQmlFile(monitoredFile, import.qualifier());
        }
    } else {
172
            // TODO: QDeclarativeDomImport::Library
173
174
175
176
177
178
179
180
181
    }
}

void SubComponentManagerPrivate::parseDirectories()
{
    if (!m_filePath.isEmpty()) {
        const QString file = m_filePath.toLocalFile();
        QFileInfo dirInfo = QFileInfo(QFileInfo(file).path());
        if (dirInfo.exists() && dirInfo.isDir())
182
            parseDirectory(dirInfo.canonicalFilePath());
183
184
    }

185
186
    foreach (const QDeclarativeDomImport &import, m_imports) {
        if (import.type() == QDeclarativeDomImport::File) {
187
188
            QFileInfo dirInfo = QFileInfo(m_filePath.resolved(import.uri()).toLocalFile());
            if (dirInfo.exists() && dirInfo.isDir()) {
189
                parseDirectory(dirInfo.canonicalFilePath());
190
            }
191
192
193
194
195
196
197
198
199
200
201
        } else {
            QString url = import.uri();
            foreach(const QString path, importPaths()) {
                url.replace(QLatin1Char('.'), QLatin1Char('/'));
                url  = path + QLatin1String("/") + url;
                QFileInfo dirInfo = QFileInfo(url);
                if (dirInfo.exists() && dirInfo.isDir()) {
                    //### todo full qualified names QString nameSpace = import.uri();
                    parseDirectory(dirInfo.canonicalFilePath(), false);
                }
            }
202
203
204
205
        }
    }
}

206
void SubComponentManagerPrivate::parseDirectory(const QString &canonicalDirPath, bool addToLibrary, const QString& qualification)
207
208
{
    if (debug)
209
        qDebug() << Q_FUNC_INFO << canonicalDirPath;
210

211
    QDir dir(canonicalDirPath);
212
213
214
215

    dir.setNameFilters(QStringList(QMLFILEPATTERN));
    dir.setFilter(QDir::Files | QDir::Readable | QDir::CaseSensitive);

216
    QList<QFileInfo> monitoredList = watchedFiles(canonicalDirPath);
217
218
219
220
221
222
223
    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()) {
224
            // QML sub components must be upper case
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
            continue;
        }
        newList << qmlFile;
    }

    qSort(monitoredList);
    qSort(newList);

    if (debug)
        qDebug() << "monitored list " << monitoredList.size() << "new list " << newList.size();
    QList<QFileInfo>::const_iterator oldIter = monitoredList.constBegin();
    QList<QFileInfo>::const_iterator newIter = newList.constBegin();

    while (oldIter != monitoredList.constEnd() && newIter != newList.constEnd()) {
        const QFileInfo oldFileInfo = *oldIter;
        const QFileInfo newFileInfo = *newIter;
        if (oldFileInfo == newFileInfo) {
            ++oldIter;
            ++newIter;
            continue;
        }
        if (oldFileInfo < newFileInfo) {
247
            foreach (const QString &qualifier, m_dirToQualifier.value(canonicalDirPath))
248
249
250
251
252
253
                unregisterQmlFile(oldFileInfo, qualifier);
            m_watcher.removePath(oldFileInfo.filePath());
            ++oldIter;
            continue;
        }
        // oldFileInfo > newFileInfo
254
        parseFile(newFileInfo.filePath(), addToLibrary, qualification);
255
256
257
258
259
        m_watcher.addPath(oldFileInfo.filePath());
        ++newIter;
    }

    while (oldIter != monitoredList.constEnd()) {
260
        foreach (const QString &qualifier, m_dirToQualifier.value(canonicalDirPath))
261
262
263
264
265
266
            unregisterQmlFile(*oldIter, qualifier);
        m_watcher.removePath(oldIter->filePath());
        ++oldIter;
    }

    while (newIter != newList.constEnd()) {
267
        parseFile(newIter->filePath(), addToLibrary, qualification);
268
        if (debug)
269
            qDebug() << "m_watcher.addPath(" << newIter->filePath() << ')';
270
271
272
273
274
        m_watcher.addPath(newIter->filePath());
        ++newIter;
    }
}

275
void SubComponentManagerPrivate::parseFile(const QString &canonicalFilePath, bool addToLibrary, const QString& /* qualification */)
276
277
{
    if (debug)
278
        qDebug() << Q_FUNC_INFO << canonicalFilePath;
279

280
    QFile file(canonicalFilePath);
281
282
283
284
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        return;
    }

285
    QDeclarativeDomDocument document;
286
    if (!document.load(&m_engine, file.readAll(), QUrl::fromLocalFile(canonicalFilePath))) {
287
        // TODO: Put the errors somewhere?
288
        qWarning() << "Could not load qml file " << canonicalFilePath;
289
290
291
        return;
    }

292
    QString dir = QFileInfo(canonicalFilePath).path();
293
    foreach (const QString &qualifier, m_dirToQualifier.values(dir)) {
294
        registerQmlFile(canonicalFilePath, qualifier, document, addToLibrary);
295
296
297
    }
}

298
299
300
301
302
void SubComponentManagerPrivate::parseFile(const QString &canonicalFilePath)
{
    parseFile(canonicalFilePath, true, QString());
}

303
304
// dirInfo must already contain a canonical path
QList<QFileInfo> SubComponentManagerPrivate::watchedFiles(const QString &canonicalDirPath)
305
306
307
308
309
{
    QList<QFileInfo> files;

    foreach (const QString &monitoredFile, m_watcher.files()) {
        QFileInfo fileInfo(monitoredFile);
310
        if (fileInfo.dir().absolutePath() == canonicalDirPath) {
311
312
313
314
315
316
317
318
319
320
321
322
323
            files.append(fileInfo);
        }
    }
    return files;
}

void SubComponentManagerPrivate::unregisterQmlFile(const QFileInfo &fileInfo, const QString &qualifier)
{
    QString componentName = fileInfo.baseName();
    if (!qualifier.isEmpty())
        componentName = qualifier + '/' + componentName;
}

324
325
static inline bool isDepricatedQtType(const QString &typeName)
{
326
    if (typeName.length() < 8)
327
328
        return false;

329
    return typeName.contains("QtQuick/");
330
331
332
}


333
void SubComponentManagerPrivate::registerQmlFile(const QFileInfo &fileInfo, const QString &qualifier,
334
                                                 const QDeclarativeDomDocument &, bool addToLibrary)
335
336
{
    QString componentName = fileInfo.baseName();
337
338
339
340
341
342
343

    if (!qualifier.isEmpty()) {
        QString fixedQualifier = qualifier;
        if (qualifier.right(1) == QLatin1String("."))
            fixedQualifier.chop(1); //remove last char if it is a dot
        componentName = fixedQualifier + '/' + componentName;
    }
344
345
346
347

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

348
349
350
    if (addToLibrary) {
        // Add file components to the library
        ItemLibraryEntry itemLibraryEntry;
351
        itemLibraryEntry.setType(componentName, -1, -1);
352
        itemLibraryEntry.setName(componentName);
353
        itemLibraryEntry.setCategory("QML Components");
354
355
        m_metaInfo.itemLibraryInfo()->addEntry(itemLibraryEntry);
    }
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
}

} // namespace Internal

/*!
  \class SubComponentManager

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

SubComponentManager::SubComponentManager(MetaInfo metaInfo, QObject *parent) :
        QObject(parent),
        m_d(new Internal::SubComponentManagerPrivate(metaInfo, this))
{
}

SubComponentManager::~SubComponentManager()
{
    delete m_d;
}

QStringList SubComponentManager::directories() const
{
    return m_d->m_watcher.directories();
}

QStringList SubComponentManager::qmlFiles() const
{
    return m_d->m_watcher.files();
}

388
static bool importEqual(const QDeclarativeDomImport &import1, const QDeclarativeDomImport &import2)
389
390
391
392
393
394
395
396
397
{
    return import1.type() == import2.type()
           && import1.uri() == import2.uri()
           && import1.version() == import2.version()
           && import1.qualifier() == import2.qualifier();
}

void SubComponentManager::update(const QUrl &filePath, const QByteArray &data)
{
398
399
    QDeclarativeEngine engine;
    QDeclarativeDomDocument document;
400

401
    QList<QDeclarativeDomImport> imports;
402
403
404
405
406
407
    if (document.load(&engine, data, filePath))
        imports = document.imports();

    update(filePath, imports);
}

408
void SubComponentManager::update(const QUrl &filePath, const QList<QDeclarativeDomImport> &imports)
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
{
    if (debug)
        qDebug() << Q_FUNC_INFO << filePath << imports.size();

    QFileInfo oldDir, newDir;

    if (!m_d->m_filePath.isEmpty()) {
        const QString file = m_d->m_filePath.toLocalFile();
        oldDir = QFileInfo(QFileInfo(file).path());
    }
    if (!filePath.isEmpty()) {
        const QString file = filePath.toLocalFile();
        newDir = QFileInfo(QFileInfo(file).path());
    }

    m_d->m_filePath = filePath;

    //
    // (implicit) import of local directory
    //
    if (oldDir != newDir) {
        if (!oldDir.filePath().isEmpty()) {
431
432
            m_d->m_dirToQualifier.remove(oldDir.canonicalFilePath(), QString());
            if (!m_d->m_dirToQualifier.contains(oldDir.canonicalFilePath()))
433
434
435
436
437
                m_d->m_watcher.removePath(oldDir.filePath());
        }

        if (!newDir.filePath().isEmpty()) {
            m_d->m_watcher.addPath(newDir.filePath());
438
            m_d->m_dirToQualifier.insertMulti(newDir.canonicalFilePath(), QString());
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
        }
    }

    //
    // Imports
    //

    // skip first list items until the lists differ
    int i = 0;
    while (i < qMin(imports.size(), m_d->m_imports.size())) {
        if (!importEqual(imports.at(i), m_d->m_imports.at(i)))
            break;
        ++i;
    }

    for (int ii = m_d->m_imports.size() - 1; ii >= i; --ii)
        m_d->removeImport(ii);

    for (int ii = i; ii < imports.size(); ++ii) {
        m_d->addImport(ii, imports.at(ii));
    }

    m_d->parseDirectories();
}

} // namespace QmlDesigner

#include "subcomponentmanager.moc"