exampleslistmodel.cpp 29.1 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
Daniel Molkentin's avatar
Daniel Molkentin committed
2
**
hjk's avatar
hjk committed
3
4
** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
Daniel Molkentin's avatar
Daniel Molkentin committed
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
Daniel Molkentin's avatar
Daniel Molkentin 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.
Daniel Molkentin's avatar
Daniel Molkentin 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
Daniel Molkentin's avatar
Daniel Molkentin committed
26
27
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
28
****************************************************************************/
Daniel Molkentin's avatar
Daniel Molkentin committed
29

30
31
#include "exampleslistmodel.h"

32
33
#include <QFile>
#include <QDir>
34
#include <QUrl>
35
#include <QXmlStreamReader>
36
37
38
39

#include <QDebug>

#include <coreplugin/icore.h>
40
#include <coreplugin/helpmanager.h>
41
#include <qtsupport/qtversionmanager.h>
42
#include <utils/qtcassert.h>
43

44
#include <utils/environment.h>
45
46
#include <projectexplorer/kitmanager.h>
#include <qtsupport/qtkitinformation.h>
47
48
49
50
51
52
53
54
55
56
#include <algorithm>

using QtSupport::QtVersionManager;
using QtSupport::BaseQtVersion;

namespace QtSupport {

namespace Internal {

ExamplesListModel::ExamplesListModel(QObject *parent) :
57
    QAbstractListModel(parent),
58
59
60
    m_updateOnQtVersionsChanged(false),
    m_initialized(false),
    m_helpInitialized(false)
61
62
63
64
65
66
67
68
69
70
71
72
{
    QHash<int, QByteArray> roleNames;
    roleNames[Name] = "name";
    roleNames[ProjectPath] = "projectPath";
    roleNames[ImageUrl] = "imageUrl";
    roleNames[Description] = "description";
    roleNames[DocUrl] = "docUrl";
    roleNames[FilesToOpen] = "filesToOpen";
    roleNames[Tags] = "tags";
    roleNames[Difficulty] = "difficulty";
    roleNames[Type] = "type";
    roleNames[HasSourceCode] = "hasSourceCode";
73
    roleNames[Dependencies] = "dependencies";
74
75
76
    roleNames[IsVideo] = "isVideo";
    roleNames[VideoUrl] = "videoUrl";
    roleNames[VideoLength] = "videoLength";
77
    roleNames[Platforms] = "platforms";
78
79
    setRoleNames(roleNames);

80
81
    connect(Core::HelpManager::instance(), SIGNAL(setupFinished()),
            SLOT(helpInitialized()));
82
    connect(QtVersionManager::instance(), SIGNAL(qtVersionsChanged(QList<int>,QList<int>,QList<int>)),
83
            this, SLOT(handleQtVersionsChanged()));
84
85
}

86
static inline QString fixStringForTags(const QString &string)
87
88
{
    QString returnString = string;
89
90
91
92
    returnString.remove(QLatin1String("<i>"));
    returnString.remove(QLatin1String("</i>"));
    returnString.remove(QLatin1String("<tt>"));
    returnString.remove(QLatin1String("</tt>"));
93
94
95
    return returnString;
}

96
97
98
99
100
101
102
103
104
static inline QStringList trimStringList(const QStringList &stringlist)
{
    QStringList returnList;
    foreach (const QString &string, stringlist)
        returnList << string.trimmed();

    return returnList;
}

105
106
107
108
109
110
111
112
113
114
115
116
117
118
static QString relativeOrInstallPath(const QString &path, const QString &manifestPath,
                                     const QString &installPath)
{
    const QChar slash = QLatin1Char('/');
    const QString relativeResolvedPath = manifestPath + slash + path;
    const QString installResolvedPath = installPath + slash + path;
    if (QFile::exists(relativeResolvedPath))
        return relativeResolvedPath;
    else if (QFile::exists(installResolvedPath))
        return installResolvedPath;
    // doesn't exist, just return relative
    return relativeResolvedPath;
}

119
120
121
122
123
static bool isValidExampleOrDemo(ExampleItem &item)
{
    static QString invalidPrefix = QLatin1String("qthelp:////"); /* means that the qthelp url
                                                                    doesn't have any namespace */
    bool ok = true;
124
    if (!item.hasSourceCode || !QFileInfo(item.projectPath).exists())
125
126
127
128
129
130
131
132
133
134
135
        ok = false;
    else if (item.imageUrl.startsWith(invalidPrefix) || !QUrl(item.imageUrl).isValid())
        ok = false;
    else if (!item.docUrl.isEmpty()
             && (item.imageUrl.startsWith(invalidPrefix) || !QUrl(item.docUrl).isValid()))
        ok = false;
    if (!ok)
        item.tags.append(QLatin1String("broken"));
    return ok || !qgetenv("QTC_DEBUG_EXAMPLESMODEL").isEmpty();
}

136
137
138
QList<ExampleItem> ExamplesListModel::parseExamples(QXmlStreamReader *reader,
                                                    const QString &projectsOffset,
                                                    const QString &examplesInstallPath)
139
140
141
{
    QList<ExampleItem> examples;
    ExampleItem item;
142
    const QChar slash = QLatin1Char('/');
143
144
145
146
147
148
149
150
151
152
    while (!reader->atEnd()) {
        switch (reader->readNext()) {
        case QXmlStreamReader::StartElement:
            if (reader->name() == QLatin1String("example")) {
                item = ExampleItem();
                item.type = Example;
                QXmlStreamAttributes attributes = reader->attributes();
                item.name = attributes.value(QLatin1String("name")).toString();
                item.projectPath = attributes.value(QLatin1String("projectPath")).toString();
                item.hasSourceCode = !item.projectPath.isEmpty();
153
                item.projectPath = relativeOrInstallPath(item.projectPath, projectsOffset, examplesInstallPath);
154
                item.imageUrl = attributes.value(QLatin1String("imageUrl")).toString();
155
156
                item.docUrl = attributes.value(QLatin1String("docUrl")).toString();
            } else if (reader->name() == QLatin1String("fileToOpen")) {
157
158
                item.filesToOpen.append(relativeOrInstallPath(reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement),
                                                              projectsOffset, examplesInstallPath));
159
            } else if (reader->name() == QLatin1String("description")) {
160
161
                item.description =  fixStringForTags(reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement));
            } else if (reader->name() == QLatin1String("dependency")) {
162
                item.dependencies.append(projectsOffset + slash + reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement));
163
            } else if (reader->name() == QLatin1String("tags")) {
164
                item.tags = trimStringList(reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement).split(QLatin1Char(','), QString::SkipEmptyParts));
165
                m_tags.append(item.tags);
166
167
168
            } else if (reader->name() == QLatin1String("platforms")) {
                item.platforms = trimStringList(reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement).split(QLatin1Char(','), QString::SkipEmptyParts));
        }
169
170
            break;
        case QXmlStreamReader::EndElement:
171
            if (reader->name() == QLatin1String("example")) {
172
                if (isValidExampleOrDemo(item))
Eike Ziller's avatar
Eike Ziller committed
173
                    examples.append(item);
174
            } else if (reader->name() == QLatin1String("examples")) {
175
                return examples;
176
            }
177
178
179
180
181
182
183
184
            break;
        default: // nothing
            break;
        }
    }
    return examples;
}

185
186
187
QList<ExampleItem> ExamplesListModel::parseDemos(QXmlStreamReader *reader,
                                                 const QString &projectsOffset,
                                                 const QString &demosInstallPath)
188
189
190
{
    QList<ExampleItem> demos;
    ExampleItem item;
191
    const QChar slash = QLatin1Char('/');
192
193
194
195
196
197
198
199
200
201
    while (!reader->atEnd()) {
        switch (reader->readNext()) {
        case QXmlStreamReader::StartElement:
            if (reader->name() == QLatin1String("demo")) {
                item = ExampleItem();
                item.type = Demo;
                QXmlStreamAttributes attributes = reader->attributes();
                item.name = attributes.value(QLatin1String("name")).toString();
                item.projectPath = attributes.value(QLatin1String("projectPath")).toString();
                item.hasSourceCode = !item.projectPath.isEmpty();
202
                item.projectPath = relativeOrInstallPath(item.projectPath, projectsOffset, demosInstallPath);
203
204
205
                item.imageUrl = attributes.value(QLatin1String("imageUrl")).toString();
                item.docUrl = attributes.value(QLatin1String("docUrl")).toString();
            } else if (reader->name() == QLatin1String("fileToOpen")) {
206
207
                item.filesToOpen.append(relativeOrInstallPath(reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement),
                                                              projectsOffset, demosInstallPath));
208
            } else if (reader->name() == QLatin1String("description")) {
209
210
                item.description =  fixStringForTags(reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement));
            } else if (reader->name() == QLatin1String("dependency")) {
211
                item.dependencies.append(projectsOffset + slash + reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement));
212
            } else if (reader->name() == QLatin1String("tags")) {
213
                item.tags = reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement).split(QLatin1Char(','));
214
215
216
            }
            break;
        case QXmlStreamReader::EndElement:
Eike Ziller's avatar
Eike Ziller committed
217
            if (reader->name() == QLatin1String("demo")) {
218
                if (isValidExampleOrDemo(item))
Eike Ziller's avatar
Eike Ziller committed
219
220
                    demos.append(item);
            } else if (reader->name() == QLatin1String("demos")) {
221
                return demos;
Eike Ziller's avatar
Eike Ziller committed
222
            }
223
224
225
226
227
228
229
230
231
232
233
234
            break;
        default: // nothing
            break;
        }
    }
    return demos;
}

QList<ExampleItem> ExamplesListModel::parseTutorials(QXmlStreamReader* reader, const QString& projectsOffset)
{
    QList<ExampleItem> tutorials;
    ExampleItem item;
235
    const QChar slash = QLatin1Char('/');
236
237
238
239
240
241
242
243
244
245
    while (!reader->atEnd()) {
        switch (reader->readNext()) {
        case QXmlStreamReader::StartElement:
            if (reader->name() == QLatin1String("tutorial")) {
                item = ExampleItem();
                item.type = Tutorial;
                QXmlStreamAttributes attributes = reader->attributes();
                item.name = attributes.value(QLatin1String("name")).toString();
                item.projectPath = attributes.value(QLatin1String("projectPath")).toString();
                item.hasSourceCode = !item.projectPath.isEmpty();
246
                item.projectPath.prepend(slash);
247
                item.projectPath.prepend(projectsOffset);
248
249
250
251
252
253
254
255
256
257
                if (attributes.hasAttribute(QLatin1String("imageUrl")))
                    item.imageUrl = attributes.value(QLatin1String("imageUrl")).toString();
                if (attributes.hasAttribute(QLatin1String("docUrl")))
                    item.docUrl = attributes.value(QLatin1String("docUrl")).toString();
                if (attributes.hasAttribute(QLatin1String("isVideo")))
                    item.isVideo = attributes.value(QLatin1String("isVideo")).toString() == QLatin1String("true");
                if (attributes.hasAttribute(QLatin1String("videoUrl")))
                    item.videoUrl = attributes.value(QLatin1String("videoUrl")).toString();
                if (attributes.hasAttribute(QLatin1String("videoLength")))
                    item.videoLength = attributes.value(QLatin1String("videoLength")).toString();
258
            } else if (reader->name() == QLatin1String("fileToOpen")) {
259
                item.filesToOpen.append(projectsOffset + slash + reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement));
260
            } else if (reader->name() == QLatin1String("description")) {
261
262
                item.description =  fixStringForTags(reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement));
            } else if (reader->name() == QLatin1String("dependency")) {
263
                item.dependencies.append(projectsOffset + slash + reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement));
264
            } else if (reader->name() == QLatin1String("tags")) {
265
                item.tags = reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement).split(QLatin1Char(','));
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
            }
            break;
        case QXmlStreamReader::EndElement:
            if (reader->name() == QLatin1String("tutorial"))
                tutorials.append(item);
            else if (reader->name() == QLatin1String("tutorials"))
                return tutorials;
            break;
        default: // nothing
            break;
        }
    }

    return tutorials;
}

282
283
284
285
286
287
288
void ExamplesListModel::handleQtVersionsChanged()
{
    if (m_updateOnQtVersionsChanged)
        updateExamples();
}

void ExamplesListModel::updateExamples()
289
290
{
    clear();
291
292
    QString examplesInstallPath;
    QString demosInstallPath;
293
294
295
296
    QString examplesFallback;
    QString demosFallback;
    QString sourceFallback;
    foreach (const QString &exampleSource,
297
298
             exampleSources(&examplesInstallPath, &demosInstallPath,
                            &examplesFallback, &demosFallback, &sourceFallback)) {
299
300
301
        QFile exampleFile(exampleSource);
        if (!exampleFile.open(QIODevice::ReadOnly)) {
            qDebug() << Q_FUNC_INFO << "Could not open file" << exampleSource;
302
            continue;
303
304
305
306
307
308
        }

        QFileInfo fi(exampleSource);
        QString offsetPath = fi.path();
        QDir examplesDir(offsetPath);
        QDir demosDir(offsetPath);
309
310
311
312
313
314
315
        if (!examplesFallback.isEmpty()) {
            // Look at Qt source directory at first,
            // since examplesPath() / demosPath() points at the build directory
            examplesDir = sourceFallback + QLatin1String("/examples");
            demosDir = sourceFallback + QLatin1String("/demos");
            // if examples or demos don't exist in source, try the directories
            // that qmake -query gave (i.e. in the build directory)
316
            if (!examplesDir.exists() || !demosDir.exists()) {
317
318
                examplesDir = examplesFallback;
                demosDir = demosFallback;
319
            }
320
321
322
323
324
325
326
        }

        QXmlStreamReader reader(&exampleFile);
        while (!reader.atEnd())
            switch (reader.readNext()) {
            case QXmlStreamReader::StartElement:
                if (reader.name() == QLatin1String("examples"))
327
                    addItems(parseExamples(&reader, examplesDir.path(), examplesInstallPath));
328
                else if (reader.name() == QLatin1String("demos"))
329
                    addItems(parseDemos(&reader, demosDir.path(), demosInstallPath));
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
                else if (reader.name() == QLatin1String("tutorials"))
                    addItems(parseTutorials(&reader, examplesDir.path()));
                break;
            default: // nothing
                break;
            }

        if (reader.hasError())
            qDebug() << "error parsing file" <<  exampleSource << "as XML document";
    }

    m_tags.sort();
    m_tags.erase(std::unique(m_tags.begin(), m_tags.end()), m_tags.end());
    emit tagsUpdated();
}

346
347
QStringList ExamplesListModel::exampleSources(QString *examplesInstallPath, QString *demosInstallPath,
                                              QString *examplesFallback, QString *demosFallback,
348
                                              QString *sourceFallback)
349
{
350
351
352
353
354
355
    QTC_CHECK(examplesFallback);
    QTC_CHECK(demosFallback);
    QTC_CHECK(sourceFallback);
    QStringList sources;
    QString resourceDir = Core::ICore::resourcePath() + QLatin1String("/welcomescreen/");

356
357
358
359
360
361
362
363
364
365
    // overriding examples with a custom XML file
    QString exampleFileEnvKey = QLatin1String("QTC_EXAMPLE_FILE");
    if (Utils::Environment::systemEnvironment().hasKey(exampleFileEnvKey)) {
        QString filePath = Utils::Environment::systemEnvironment().value(exampleFileEnvKey);
        if (filePath.endsWith(QLatin1String(".xml")) && QFileInfo(filePath).exists()) {
            sources.append(filePath);
            return sources;
        }
    }

366
367
    // Qt Creator shipped tutorials
    sources << (resourceDir + QLatin1String("/qtcreator_tutorials.xml"));
368

369
    // Read keys from SDK installer
hjk's avatar
hjk committed
370
    QSettings *settings = Core::ICore::settings(QSettings::SystemScope);
371
    int size = settings->beginReadArray(QLatin1String("ExampleManifests"));
372
373
    for (int i = 0; i < size; ++i) {
        settings->setArrayIndex(i);
374
        sources.append(settings->value(QLatin1String("Location")).toString());
375
376
    }
    settings->endArray();
377
378
379
380
381
    // if the installer set something, that's enough for us
    if (size > 0)
        return sources;

    // try to find a suitable Qt version
Friedemann Kleint's avatar
Friedemann Kleint committed
382
    m_updateOnQtVersionsChanged = true; // this must be updated when the Qt versions change
383
384
385
386
387
388
    // fallbacks are passed back if no example manifest is found
    // and we fallback to Qt Creator's shipped manifest (e.g. only old Qt Versions found)
    QString potentialExamplesFallback;
    QString potentialDemosFallback;
    QString potentialSourceFallback;
    const QStringList pattern(QLatin1String("*.xml"));
389

390
    // prioritize default qt version
391
    QtVersionManager *versionManager = QtVersionManager::instance();
392
393
394
395
396
    QList <BaseQtVersion *> qtVersions = versionManager->validVersions();
    ProjectExplorer::Kit *defaultKit = ProjectExplorer::KitManager::instance()->defaultKit();
    BaseQtVersion *defaultVersion = QtKitInformation::qtVersion(defaultKit);
    if (defaultVersion && qtVersions.contains(defaultVersion))
        qtVersions.move(qtVersions.indexOf(defaultVersion), 0);
397

398
    foreach (BaseQtVersion *version, qtVersions) {
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
        // qt5 with examples OR demos manifest
        if (version->qtVersion().majorVersion == 5 && (version->hasExamples() || version->hasDemos())) {
            // examples directory in Qt5 is under the qtbase submodule,
            // search other submodule directories for further manifest files
            QDir qt5docPath = QDir(version->documentationPath());
            const QStringList examplesPattern(QLatin1String("examples-manifest.xml"));
            const QStringList demosPattern(QLatin1String("demos-manifest.xml"));
            QFileInfoList fis;
            foreach (QFileInfo subDir, qt5docPath.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) {
                if (version->hasExamples())
                    fis << QDir(subDir.absoluteFilePath()).entryInfoList(examplesPattern);
                if (version->hasDemos())
                    fis << QDir(subDir.absoluteFilePath()).entryInfoList(demosPattern);
            }
            if (!fis.isEmpty()) {
                foreach (const QFileInfo &fi, fis)
                    sources.append(fi.filePath());
416
417
418
419
                if (examplesInstallPath)
                    *examplesInstallPath = version->examplesPath();
                if (demosInstallPath)
                    *demosInstallPath = version->demosPath();
420
421
422
423
                return sources;
            }
        }

424
425
426
427
428
429
430
431
432
433
        QFileInfoList fis;
        if (version->hasExamples())
            fis << QDir(version->examplesPath()).entryInfoList(pattern);
        if (version->hasDemos())
            fis << QDir(version->demosPath()).entryInfoList(pattern);
        if (!fis.isEmpty()) {
            foreach (const QFileInfo &fi, fis)
                sources.append(fi.filePath());
            return sources;
        }
434
435
        // check if this Qt version would be the preferred fallback, Qt 4 only
        if (version->qtVersion().majorVersion == 4 && version->hasExamples() && version->hasDemos()) { // cached, so no performance hit
436
            if (potentialExamplesFallback.isEmpty()) {
437
438
439
                potentialExamplesFallback = version->examplesPath();
                potentialDemosFallback = version->demosPath();
                potentialSourceFallback = version->sourcePath().toString();
440
            }
441
442
443
        }
    }

444
445
    if (!potentialExamplesFallback.isEmpty()) {
        // We didn't find a manifest, use Creator-provided XML file with fall back Qt version
446
        // qDebug() << Q_FUNC_INFO << "falling through to Creator-provided XML file";
447
        sources << QString(resourceDir + QLatin1String("/examples_fallback.xml"));
448
449
450
451
452
453
        if (examplesFallback)
            *examplesFallback = potentialExamplesFallback;
        if (demosFallback)
            *demosFallback = potentialDemosFallback;
        if (sourceFallback)
            *sourceFallback = potentialSourceFallback;
454
    }
455
    return sources;
456
457
458
459
460
461
462
463
464
465
466
467
468
469
}

void ExamplesListModel::clear()
{
    if (exampleItems.count() > 0) {
        beginRemoveRows(QModelIndex(), 0,  exampleItems.size()-1);
        exampleItems.clear();
        endRemoveRows();
    }
    m_tags.clear();
}

void ExamplesListModel::addItems(const QList<ExampleItem> &newItems)
{
470
471
    if (newItems.isEmpty())
        return;
472
473
474
475
476
477
478
    beginInsertRows(QModelIndex(), exampleItems.size(), exampleItems.size() - 1 + newItems.size());
    exampleItems.append(newItems);
    endInsertRows();
}

int ExamplesListModel::rowCount(const QModelIndex &) const
{
479
    ensureInitialized();
480
481
482
483
484
    return exampleItems.size();
}

QVariant ExamplesListModel::data(const QModelIndex &index, int role) const
{
485
    ensureInitialized();
486
487
488
489
490
491
492
493
494
    if (!index.isValid() || index.row()+1 > exampleItems.count()) {
        qDebug() << Q_FUNC_INFO << "invalid index requested";
        return QVariant();
    }

    ExampleItem item = exampleItems.at(index.row());
    switch (role)
    {
    case Qt::DisplayRole: // for search only
495
        return QString(item.name + QLatin1Char(' ') + item.tags.join(QLatin1String(" ")));
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
    case Name:
        return item.name;
    case ProjectPath:
        return item.projectPath;
    case Description:
        return item.description;
    case ImageUrl:
        return item.imageUrl;
    case DocUrl:
        return item.docUrl;
    case FilesToOpen:
        return item.filesToOpen;
    case Tags:
        return item.tags;
    case Difficulty:
        return item.difficulty;
512
513
    case Dependencies:
        return item.dependencies;
514
515
516
517
    case HasSourceCode:
        return item.hasSourceCode;
    case Type:
        return item.type;
518
519
520
521
522
523
    case IsVideo:
        return item.isVideo;
    case VideoUrl:
        return item.videoUrl;
    case VideoLength:
        return item.videoLength;
524
525
    case Platforms:
        return item.platforms;
526
527
528
529
530
531
532
    default:
        qDebug() << Q_FUNC_INFO << "role type not supported";
        return QVariant();
    }

}

533
534
535
536
537
538
QStringList ExamplesListModel::tags() const
{
    ensureInitialized();
    return m_tags;
}

539
540
void ExamplesListModel::helpInitialized()
{
541
542
543
    m_helpInitialized = true;
    if (m_initialized) // if we are already initialized we need to update nevertheless
        updateExamples();
544
545
}

546
547
548
549
550
551
552
553
void ExamplesListModel::ensureInitialized() const
{
    if (m_initialized || !m_helpInitialized)
        return;
    ExamplesListModel *that = const_cast<ExamplesListModel *>(this);
    that->m_initialized = true;
    that->updateExamples();
}
554

555
ExamplesListModelFilter::ExamplesListModelFilter(ExamplesListModel *sourceModel, QObject *parent) :
thohartm's avatar
thohartm committed
556
    QSortFilterProxyModel(parent), m_showTutorialsOnly(true), m_sourceModel(sourceModel), m_timerId(0)
557
558
{
    connect(this, SIGNAL(showTutorialsOnlyChanged()), SLOT(updateFilter()));
559
    setSourceModel(m_sourceModel);
560
561
562
563
}

void ExamplesListModelFilter::updateFilter()
{
thohartm's avatar
thohartm committed
564
565
566
567
568
569
    ExamplesListModel *exampleListModel = qobject_cast<ExamplesListModel*>(sourceModel());
    if (exampleListModel) {
        exampleListModel->beginReset();
        invalidateFilter();
        exampleListModel->endReset();
    }
570
571
}

572
573
574
575
576
577
578
579
580
581
bool containsSubString(const QStringList& list, const QString& substr, Qt::CaseSensitivity cs)
{
    foreach (const QString &elem, list) {
        if (elem.contains(substr, cs))
            return true;
    }

    return false;
}

582
583
584
585
586
587
bool ExamplesListModelFilter::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
    if (m_showTutorialsOnly) {
        int type = sourceModel()->index(sourceRow, 0, sourceParent).data(Type).toInt();
        if (type != Tutorial)
            return false;
588
589
    }

590
591
592
593
594
595
    if (!m_showTutorialsOnly) {
        int type = sourceModel()->index(sourceRow, 0, sourceParent).data(Type).toInt();
        if (type != Example)
            return false;
    }

596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
    const QStringList tags = sourceModel()->index(sourceRow, 0, sourceParent).data(Tags).toStringList();

    if (!m_filterTags.isEmpty()) {
        foreach(const QString &tag, m_filterTags) {
            if (!tags.contains(tag, Qt::CaseInsensitive))
                return false;
        }
        return true;
    }

    if (!m_searchString.isEmpty()) {
        const QString description = sourceModel()->index(sourceRow, 0, sourceParent).data(Description).toString();
        const QString name = sourceModel()->index(sourceRow, 0, sourceParent).data(Name).toString();


        foreach(const QString &subString, m_searchString) {
            bool wordMatch = false;
            wordMatch |= (bool)name.contains(subString, Qt::CaseInsensitive);
            if (wordMatch)
                continue;
            wordMatch |= containsSubString(tags, subString, Qt::CaseInsensitive);
            if (wordMatch)
                continue;
            wordMatch |= (bool)description.contains(subString, Qt::CaseInsensitive);
            if (!wordMatch)
                return false;
        }
623
624
625
626
627
628
629
630
631
    }

    bool ok = QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent);
    if (!ok)
        return false;

    return true;
}

632
633
634
635
636
637
638
639
640
641
642
643
int ExamplesListModelFilter::rowCount(const QModelIndex &parent) const
{
    m_sourceModel->ensureInitialized();
    return QSortFilterProxyModel::rowCount(parent);
}

QVariant ExamplesListModelFilter::data(const QModelIndex &index, int role) const
{
    m_sourceModel->ensureInitialized();
    return QSortFilterProxyModel::data(index, role);
}

644
645
646
647
648
649
void ExamplesListModelFilter::setShowTutorialsOnly(bool showTutorialsOnly)
{
    m_showTutorialsOnly = showTutorialsOnly;
    emit showTutorialsOnlyChanged();
}

thohartm's avatar
thohartm committed
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
void ExamplesListModelFilter::delayedUpdateFilter()
{
    if (m_timerId != 0)
        killTimer(m_timerId);

    m_timerId = startTimer(320);
}

void ExamplesListModelFilter::timerEvent(QTimerEvent *timerEvent)
{
    if (m_timerId == timerEvent->timerId()) {
        updateFilter();
        killTimer(m_timerId);
        m_timerId = 0;
    }
}

667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
struct SearchStringLexer {
    QString code;
    const QChar *codePtr;
    QChar yychar;
    QString yytext;

    enum TokenKind {
        END_OF_STRING = 0,
        TAG,
        STRING_LITERAL,
        UNKNOWN
    };

    inline void yyinp() { yychar = *codePtr++; }

    SearchStringLexer(const QString &code)
        : code(code)
        , codePtr(code.unicode())
685
        , yychar(QLatin1Char(' ')) { }
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710

    int operator()() { return yylex(); }

    int yylex() {
        while (yychar.isSpace())
            yyinp(); // skip all the spaces

        yytext.clear();

        if (yychar.isNull())
            return END_OF_STRING;

        QChar ch = yychar;
        yyinp();

        switch (ch.unicode()) {
        case '"':
        case '\'':
        {
            const QChar quote = ch;
            yytext.clear();
            while (!yychar.isNull()) {
                if (yychar == quote) {
                    yyinp();
                    break;
711
                } if (yychar == QLatin1Char('\\')) {
712
713
                    yyinp();
                    switch (yychar.unicode()) {
714
715
716
                    case '"': yytext += QLatin1Char('"'); yyinp(); break;
                    case '\'': yytext += QLatin1Char('\''); yyinp(); break;
                    case '\\': yytext += QLatin1Char('\\'); yyinp(); break;
717
718
719
720
721
722
723
724
725
726
                    }
                } else {
                    yytext += yychar;
                    yyinp();
                }
            }
            return STRING_LITERAL;
        }

        default:
727
            if (ch.isLetterOrNumber() || ch == QLatin1Char('_')) {
728
729
                yytext.clear();
                yytext += ch;
730
                while (yychar.isLetterOrNumber() || yychar == QLatin1Char('_')) {
731
732
733
                    yytext += yychar;
                    yyinp();
                }
734
                if (yychar == QLatin1Char(':') && yytext == QLatin1String("tag")) {
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
                    yyinp();
                    return TAG;
                }
                return STRING_LITERAL;
            }
        }

        yytext += ch;
        return UNKNOWN;
    }
};

void ExamplesListModelFilter::parseSearchString(const QString &arg)
{
    QStringList tags;
    QStringList searchTerms;
    SearchStringLexer lex(arg);
    bool isTag = false;
    while (int tk = lex()) {
        if (tk == SearchStringLexer::TAG) {
            isTag = true;
            searchTerms.append(lex.yytext);
        }

        if (tk == SearchStringLexer::STRING_LITERAL) {
            if (isTag) {
                searchTerms.pop_back();
                tags.append(lex.yytext);
                isTag = false;
            } else {
                searchTerms.append(lex.yytext);
            }
        }
    }

    setSearchStrings(searchTerms);
    setFilterTags(tags);
thohartm's avatar
thohartm committed
772
    delayedUpdateFilter();
773
774
}

775
776
} // namespace Internal
} // namespace QtSupport