Files
qt-creator/src/plugins/qtsupport/exampleslistmodel.cpp
Eike Ziller a3f5fa09f5 Show examples also of "invalid" Qt versions
As long as we are able to find examples or demos for them.
Android Qt versions can be "invalid" until the device settings are
fixed, but that doesn't prevent us from showing examples.

Task-number: QTCREATORBUG-23058
Change-Id: I8d3351a3f31727b062b37f5bd462709a9d6ef9dd
Reviewed-by: BogDan Vatra <bogdan@kdab.com>
Reviewed-by: David Schulz <david.schulz@qt.io>
2019-10-08 13:15:48 +00:00

878 lines
32 KiB
C++

/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** 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 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.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** 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.
**
****************************************************************************/
#include "exampleslistmodel.h"
#include "screenshotcropper.h"
#include <QBuffer>
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QFutureWatcher>
#include <QImageReader>
#include <QPixmapCache>
#include <QUrl>
#include <QXmlStreamReader>
#include <coreplugin/helpmanager.h>
#include <coreplugin/icore.h>
#include <qtsupport/qtkitinformation.h>
#include <qtsupport/qtversionmanager.h>
#include <utils/algorithm.h>
#include <utils/environment.h>
#include <utils/fileutils.h>
#include <utils/qtcassert.h>
#include <utils/stylehelper.h>
#include <algorithm>
namespace QtSupport {
namespace Internal {
const QSize ExamplesListModel::exampleImageSize(188, 145);
static bool debugExamples()
{
static bool isDebugging = qEnvironmentVariableIsSet("QTC_DEBUG_EXAMPLESMODEL");
return isDebugging;
}
static const char kSelectedExampleSetKey[] = "WelcomePage/SelectedExampleSet";
void ExampleSetModel::writeCurrentIdToSettings(int currentIndex) const
{
QSettings *settings = Core::ICore::settings();
settings->setValue(QLatin1String(kSelectedExampleSetKey), getId(currentIndex));
}
int ExampleSetModel::readCurrentIndexFromSettings() const
{
QVariant id = Core::ICore::settings()->value(QLatin1String(kSelectedExampleSetKey));
for (int i=0; i < rowCount(); i++) {
if (id == getId(i))
return i;
}
return -1;
}
ExampleSetModel::ExampleSetModel()
{
// read extra example sets settings
QSettings *settings = Core::ICore::settings();
const QStringList list = settings->value("Help/InstalledExamples", QStringList()).toStringList();
if (debugExamples())
qWarning() << "Reading Help/InstalledExamples from settings:" << list;
for (const QString &item : list) {
const QStringList &parts = item.split(QLatin1Char('|'));
if (parts.size() < 3) {
if (debugExamples())
qWarning() << "Item" << item << "has less than 3 parts (separated by '|'):" << parts;
continue;
}
ExtraExampleSet set;
set.displayName = parts.at(0);
set.manifestPath = parts.at(1);
set.examplesPath = parts.at(2);
QFileInfo fi(set.manifestPath);
if (!fi.isDir() || !fi.isReadable()) {
if (debugExamples())
qWarning() << "Manifest path " << set.manifestPath << "is not a readable directory, ignoring";
continue;
}
m_extraExampleSets.append(set);
if (debugExamples()) {
qWarning() << "Adding examples set displayName=" << set.displayName
<< ", manifestPath=" << set.manifestPath
<< ", examplesPath=" << set.examplesPath;
}
}
connect(QtVersionManager::instance(), &QtVersionManager::qtVersionsLoaded,
this, &ExampleSetModel::qtVersionManagerLoaded);
connect(Core::HelpManager::Signals::instance(),
&Core::HelpManager::Signals::setupFinished,
this,
&ExampleSetModel::helpManagerInitialized);
}
void ExampleSetModel::recreateModel(const QList<BaseQtVersion *> &qtVersions)
{
beginResetModel();
clear();
QSet<QString> extraManifestDirs;
for (int i = 0; i < m_extraExampleSets.size(); ++i) {
const ExtraExampleSet &set = m_extraExampleSets.at(i);
auto newItem = new QStandardItem();
newItem->setData(set.displayName, Qt::DisplayRole);
newItem->setData(set.displayName, Qt::UserRole + 1);
newItem->setData(QVariant(), Qt::UserRole + 2);
newItem->setData(i, Qt::UserRole + 3);
appendRow(newItem);
extraManifestDirs.insert(set.manifestPath);
}
foreach (BaseQtVersion *version, qtVersions) {
// sanitize away qt versions that have already been added through extra sets
if (extraManifestDirs.contains(version->docsPath().toString())) {
if (debugExamples()) {
qWarning() << "Not showing Qt version because manifest path is already added through InstalledExamples settings:"
<< version->displayName();
}
continue;
}
auto newItem = new QStandardItem();
newItem->setData(version->displayName(), Qt::DisplayRole);
newItem->setData(version->displayName(), Qt::UserRole + 1);
newItem->setData(version->uniqueId(), Qt::UserRole + 2);
newItem->setData(QVariant(), Qt::UserRole + 3);
appendRow(newItem);
}
endResetModel();
}
int ExampleSetModel::indexForQtVersion(BaseQtVersion *qtVersion) const
{
// return either the entry with the same QtId, or an extra example set with same path
if (!qtVersion)
return -1;
// check for Qt version
for (int i = 0; i < rowCount(); ++i) {
if (getType(i) == QtExampleSet && getQtId(i) == qtVersion->uniqueId())
return i;
}
// check for extra set
const QString &documentationPath = qtVersion->docsPath().toString();
for (int i = 0; i < rowCount(); ++i) {
if (getType(i) == ExtraExampleSetType
&& m_extraExampleSets.at(getExtraExampleSetIndex(i)).manifestPath == documentationPath)
return i;
}
return -1;
}
QVariant ExampleSetModel::getDisplayName(int i) const
{
if (i < 0 || i >= rowCount())
return QVariant();
return data(index(i, 0), Qt::UserRole + 1);
}
// id is either the Qt version uniqueId, or the display name of the extra example set
QVariant ExampleSetModel::getId(int i) const
{
if (i < 0 || i >= rowCount())
return QVariant();
QModelIndex modelIndex = index(i, 0);
QVariant variant = data(modelIndex, Qt::UserRole + 2);
if (variant.isValid()) // set from qt version
return variant;
return getDisplayName(i);
}
ExampleSetModel::ExampleSetType ExampleSetModel::getType(int i) const
{
if (i < 0 || i >= rowCount())
return InvalidExampleSet;
QModelIndex modelIndex = index(i, 0);
QVariant variant = data(modelIndex, Qt::UserRole + 2); /*Qt version uniqueId*/
if (variant.isValid())
return QtExampleSet;
return ExtraExampleSetType;
}
int ExampleSetModel::getQtId(int i) const
{
QTC_ASSERT(i >= 0, return -1);
QModelIndex modelIndex = index(i, 0);
QVariant variant = data(modelIndex, Qt::UserRole + 2);
QTC_ASSERT(variant.isValid(), return -1);
QTC_ASSERT(variant.canConvert<int>(), return -1);
return variant.toInt();
}
int ExampleSetModel::getExtraExampleSetIndex(int i) const
{
QTC_ASSERT(i >= 0, return -1);
QModelIndex modelIndex = index(i, 0);
QVariant variant = data(modelIndex, Qt::UserRole + 3);
QTC_ASSERT(variant.isValid(), return -1);
QTC_ASSERT(variant.canConvert<int>(), return -1);
return variant.toInt();
}
ExamplesListModel::ExamplesListModel(QObject *parent)
: QAbstractListModel(parent)
{
connect(&m_exampleSetModel, &ExampleSetModel::selectedExampleSetChanged,
this, &ExamplesListModel::updateExamples);
connect(Core::HelpManager::Signals::instance(),
&Core::HelpManager::Signals::documentationChanged,
this,
&ExamplesListModel::updateExamples);
}
static QString fixStringForTags(const QString &string)
{
QString returnString = string;
returnString.remove(QLatin1String("<i>"));
returnString.remove(QLatin1String("</i>"));
returnString.remove(QLatin1String("<tt>"));
returnString.remove(QLatin1String("</tt>"));
return returnString;
}
static QStringList trimStringList(const QStringList &stringlist)
{
return Utils::transform(stringlist, [](const QString &str) { return str.trimmed(); });
}
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;
if (QFile::exists(installResolvedPath))
return installResolvedPath;
// doesn't exist, just return relative
return relativeResolvedPath;
}
static bool isValidExampleOrDemo(ExampleItem &item)
{
static QString invalidPrefix = QLatin1String("qthelp:////"); /* means that the qthelp url
doesn't have any namespace */
QString reason;
bool ok = true;
if (!item.hasSourceCode || !QFileInfo::exists(item.projectPath)) {
ok = false;
reason = QString::fromLatin1("projectPath \"%1\" empty or does not exist").arg(item.projectPath);
} else if (item.imageUrl.startsWith(invalidPrefix) || !QUrl(item.imageUrl).isValid()) {
ok = false;
reason = QString::fromLatin1("imageUrl \"%1\" not valid").arg(item.imageUrl);
} else if (!item.docUrl.isEmpty()
&& (item.docUrl.startsWith(invalidPrefix) || !QUrl(item.docUrl).isValid())) {
ok = false;
reason = QString::fromLatin1("docUrl \"%1\" non-empty but not valid").arg(item.docUrl);
}
if (!ok) {
item.tags.append(QLatin1String("broken"));
if (debugExamples())
qWarning() << QString::fromLatin1("ERROR: Item \"%1\" broken: %2").arg(item.name, reason);
}
if (debugExamples() && item.description.isEmpty())
qWarning() << QString::fromLatin1("WARNING: Item \"%1\" has no description").arg(item.name);
return ok || debugExamples();
}
void ExamplesListModel::parseExamples(QXmlStreamReader *reader,
const QString &projectsOffset, const QString &examplesInstallPath)
{
ExampleItem item;
const QChar slash = QLatin1Char('/');
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();
item.projectPath = relativeOrInstallPath(item.projectPath, projectsOffset, examplesInstallPath);
item.imageUrl = attributes.value(QLatin1String("imageUrl")).toString();
QPixmapCache::remove(item.imageUrl);
item.docUrl = attributes.value(QLatin1String("docUrl")).toString();
item.isHighlighted = attributes.value(QLatin1String("isHighlighted")).toString() == QLatin1String("true");
} else if (reader->name() == QLatin1String("fileToOpen")) {
const QString mainFileAttribute = reader->attributes().value(
QLatin1String("mainFile")).toString();
const QString filePath = relativeOrInstallPath(
reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement),
projectsOffset, examplesInstallPath);
item.filesToOpen.append(filePath);
if (mainFileAttribute.compare(QLatin1String("true"), Qt::CaseInsensitive) == 0)
item.mainFile = filePath;
} else if (reader->name() == QLatin1String("description")) {
item.description = fixStringForTags(reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement));
} else if (reader->name() == QLatin1String("dependency")) {
item.dependencies.append(projectsOffset + slash + reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement));
} else if (reader->name() == QLatin1String("tags")) {
item.tags = trimStringList(reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement).split(QLatin1Char(','), QString::SkipEmptyParts));
} else if (reader->name() == QLatin1String("platforms")) {
item.platforms = trimStringList(reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement).split(QLatin1Char(','), QString::SkipEmptyParts));
}
break;
case QXmlStreamReader::EndElement:
if (reader->name() == QLatin1String("example")) {
if (isValidExampleOrDemo(item))
m_exampleItems.append(item);
} else if (reader->name() == QLatin1String("examples")) {
return;
}
break;
default: // nothing
break;
}
}
}
void ExamplesListModel::parseDemos(QXmlStreamReader *reader,
const QString &projectsOffset, const QString &demosInstallPath)
{
ExampleItem item;
const QChar slash = QLatin1Char('/');
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();
item.projectPath = relativeOrInstallPath(item.projectPath, projectsOffset, demosInstallPath);
item.imageUrl = attributes.value(QLatin1String("imageUrl")).toString();
QPixmapCache::remove(item.imageUrl);
item.docUrl = attributes.value(QLatin1String("docUrl")).toString();
item.isHighlighted = attributes.value(QLatin1String("isHighlighted")).toString() == QLatin1String("true");
} else if (reader->name() == QLatin1String("fileToOpen")) {
item.filesToOpen.append(relativeOrInstallPath(reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement),
projectsOffset, demosInstallPath));
} else if (reader->name() == QLatin1String("description")) {
item.description = fixStringForTags(reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement));
} else if (reader->name() == QLatin1String("dependency")) {
item.dependencies.append(projectsOffset + slash + reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement));
} else if (reader->name() == QLatin1String("tags")) {
item.tags = reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement).split(QLatin1Char(','));
}
break;
case QXmlStreamReader::EndElement:
if (reader->name() == QLatin1String("demo")) {
if (isValidExampleOrDemo(item))
m_exampleItems.append(item);
} else if (reader->name() == QLatin1String("demos")) {
return;
}
break;
default: // nothing
break;
}
}
}
void ExamplesListModel::parseTutorials(QXmlStreamReader *reader, const QString &projectsOffset)
{
ExampleItem item;
const QChar slash = QLatin1Char('/');
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();
item.projectPath.prepend(slash);
item.projectPath.prepend(projectsOffset);
item.imageUrl = Utils::StyleHelper::dpiSpecificImageFile(
attributes.value(QLatin1String("imageUrl")).toString());
QPixmapCache::remove(item.imageUrl);
item.docUrl = attributes.value(QLatin1String("docUrl")).toString();
item.isVideo = attributes.value(QLatin1String("isVideo")).toString() == QLatin1String("true");
item.videoUrl = attributes.value(QLatin1String("videoUrl")).toString();
item.videoLength = attributes.value(QLatin1String("videoLength")).toString();
} else if (reader->name() == QLatin1String("fileToOpen")) {
item.filesToOpen.append(projectsOffset + slash + reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement));
} else if (reader->name() == QLatin1String("description")) {
item.description = fixStringForTags(reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement));
} else if (reader->name() == QLatin1String("dependency")) {
item.dependencies.append(projectsOffset + slash + reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement));
} else if (reader->name() == QLatin1String("tags")) {
item.tags = reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement).split(QLatin1Char(','));
}
break;
case QXmlStreamReader::EndElement:
if (reader->name() == QLatin1String("tutorial"))
m_exampleItems.append(item);
else if (reader->name() == QLatin1String("tutorials"))
return;
break;
default: // nothing
break;
}
}
}
static QString resourcePath()
{
// normalize paths so QML doesn't freak out if it's wrongly capitalized on Windows
return Utils::FileUtils::normalizePathName(Core::ICore::resourcePath());
}
void ExamplesListModel::updateExamples()
{
QString examplesInstallPath;
QString demosInstallPath;
QStringList sources = m_exampleSetModel.exampleSources(&examplesInstallPath, &demosInstallPath);
beginResetModel();
m_exampleItems.clear();
foreach (const QString &exampleSource, sources) {
QFile exampleFile(exampleSource);
if (!exampleFile.open(QIODevice::ReadOnly)) {
if (debugExamples())
qWarning() << "ERROR: Could not open file" << exampleSource;
continue;
}
QFileInfo fi(exampleSource);
QString offsetPath = fi.path();
QDir examplesDir(offsetPath);
QDir demosDir(offsetPath);
if (debugExamples())
qWarning() << QString::fromLatin1("Reading file \"%1\"...").arg(fi.absoluteFilePath());
QXmlStreamReader reader(&exampleFile);
while (!reader.atEnd())
switch (reader.readNext()) {
case QXmlStreamReader::StartElement:
if (reader.name() == QLatin1String("examples"))
parseExamples(&reader, examplesDir.path(), examplesInstallPath);
else if (reader.name() == QLatin1String("demos"))
parseDemos(&reader, demosDir.path(), demosInstallPath);
else if (reader.name() == QLatin1String("tutorials"))
parseTutorials(&reader, examplesDir.path());
break;
default: // nothing
break;
}
if (reader.hasError() && debugExamples())
qWarning() << QString::fromLatin1("ERROR: Could not parse file as XML document (%1)").arg(exampleSource);
}
endResetModel();
}
void ExampleSetModel::updateQtVersionList()
{
QList<BaseQtVersion *> versions = QtVersionManager::sortVersions(QtVersionManager::versions(
[](const BaseQtVersion *v) { return v->hasExamples() || v->hasDemos(); }));
// prioritize default qt version
ProjectExplorer::Kit *defaultKit = ProjectExplorer::KitManager::defaultKit();
BaseQtVersion *defaultVersion = QtKitAspect::qtVersion(defaultKit);
if (defaultVersion && versions.contains(defaultVersion))
versions.move(versions.indexOf(defaultVersion), 0);
recreateModel(versions);
int currentIndex = m_selectedExampleSetIndex;
if (currentIndex < 0) // reset from settings
currentIndex = readCurrentIndexFromSettings();
ExampleSetModel::ExampleSetType currentType = getType(currentIndex);
if (currentType == ExampleSetModel::InvalidExampleSet) {
// select examples corresponding to 'highest' Qt version
BaseQtVersion *highestQt = findHighestQtVersion(versions);
currentIndex = indexForQtVersion(highestQt);
} else if (currentType == ExampleSetModel::QtExampleSet) {
// try to select the previously selected Qt version, or
// select examples corresponding to 'highest' Qt version
int currentQtId = getQtId(currentIndex);
BaseQtVersion *newQtVersion = QtVersionManager::version(currentQtId);
if (!newQtVersion)
newQtVersion = findHighestQtVersion(versions);
currentIndex = indexForQtVersion(newQtVersion);
} // nothing to do for extra example sets
selectExampleSet(currentIndex);
emit selectedExampleSetChanged(currentIndex);
}
BaseQtVersion *ExampleSetModel::findHighestQtVersion(const QList<BaseQtVersion *> &versions) const
{
BaseQtVersion *newVersion = nullptr;
for (BaseQtVersion *version : versions) {
if (!newVersion) {
newVersion = version;
} else {
if (version->qtVersion() > newVersion->qtVersion()) {
newVersion = version;
} else if (version->qtVersion() == newVersion->qtVersion()
&& version->uniqueId() < newVersion->uniqueId()) {
newVersion = version;
}
}
}
if (!newVersion && !versions.isEmpty())
newVersion = versions.first();
return newVersion;
}
QStringList ExampleSetModel::exampleSources(QString *examplesInstallPath, QString *demosInstallPath)
{
QStringList sources;
// Qt Creator shipped tutorials
sources << ":/qtsupport/qtcreator_tutorials.xml";
QString examplesPath;
QString demosPath;
QString manifestScanPath;
ExampleSetModel::ExampleSetType currentType = getType(m_selectedExampleSetIndex);
if (currentType == ExampleSetModel::ExtraExampleSetType) {
int index = getExtraExampleSetIndex(m_selectedExampleSetIndex);
ExtraExampleSet exampleSet = m_extraExampleSets.at(index);
manifestScanPath = exampleSet.manifestPath;
examplesPath = exampleSet.examplesPath;
demosPath = exampleSet.examplesPath;
} else if (currentType == ExampleSetModel::QtExampleSet) {
int qtId = getQtId(m_selectedExampleSetIndex);
foreach (BaseQtVersion *version, QtVersionManager::versions()) {
if (version->uniqueId() == qtId) {
manifestScanPath = version->docsPath().toString();
examplesPath = version->examplesPath().toString();
demosPath = version->demosPath().toString();
break;
}
}
}
if (!manifestScanPath.isEmpty()) {
// search for examples-manifest.xml, demos-manifest.xml in <path>/*/
QDir dir = QDir(manifestScanPath);
const QStringList examplesPattern(QLatin1String("examples-manifest.xml"));
const QStringList demosPattern(QLatin1String("demos-manifest.xml"));
QFileInfoList fis;
foreach (QFileInfo subDir, dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) {
fis << QDir(subDir.absoluteFilePath()).entryInfoList(examplesPattern);
fis << QDir(subDir.absoluteFilePath()).entryInfoList(demosPattern);
}
foreach (const QFileInfo &fi, fis)
sources.append(fi.filePath());
}
if (examplesInstallPath)
*examplesInstallPath = examplesPath;
if (demosInstallPath)
*demosInstallPath = demosPath;
return sources;
}
int ExamplesListModel::rowCount(const QModelIndex &) const
{
return m_exampleItems.size();
}
QString prefixForItem(const ExampleItem &item)
{
if (item.isHighlighted)
return QLatin1String("0000 ");
return QString();
}
QVariant ExamplesListModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || index.row() >= m_exampleItems.count())
return QVariant();
const ExampleItem &item = m_exampleItems.at(index.row());
switch (role)
{
case Qt::DisplayRole: // for search only
return QString(prefixForItem(item) + item.name + ' ' + item.tags.join(' '));
case ExampleItemRole:
return QVariant::fromValue<ExampleItem>(item);
case ExampleImageRole: {
QPixmap pixmap;
if (QPixmapCache::find(item.imageUrl, &pixmap))
return pixmap;
pixmap.load(item.imageUrl);
if (pixmap.isNull())
pixmap.load(resourcePath() + "/welcomescreen/widgets/" + item.imageUrl);
if (pixmap.isNull()) {
QByteArray fetchedData = Core::HelpManager::fileData(item.imageUrl);
if (!fetchedData.isEmpty()) {
QBuffer imgBuffer(&fetchedData);
imgBuffer.open(QIODevice::ReadOnly);
QImageReader reader(&imgBuffer);
QImage img = reader.read();
img = ScreenshotCropper::croppedImage(img, item.imageUrl,
ExamplesListModel::exampleImageSize);
pixmap = QPixmap::fromImage(img);
}
}
QPixmapCache::insert(item.imageUrl, pixmap);
return pixmap;
}
default:
return QVariant();
}
}
void ExampleSetModel::selectExampleSet(int index)
{
if (index != m_selectedExampleSetIndex) {
m_selectedExampleSetIndex = index;
writeCurrentIdToSettings(m_selectedExampleSetIndex);
emit selectedExampleSetChanged(m_selectedExampleSetIndex);
}
}
void ExampleSetModel::qtVersionManagerLoaded()
{
m_qtVersionManagerInitialized = true;
tryToInitialize();
}
void ExampleSetModel::helpManagerInitialized()
{
m_helpManagerInitialized = true;
tryToInitialize();
}
void ExampleSetModel::tryToInitialize()
{
if (m_initalized)
return;
if (!m_qtVersionManagerInitialized)
return;
if (!m_helpManagerInitialized)
return;
m_initalized = true;
connect(QtVersionManager::instance(), &QtVersionManager::qtVersionsChanged,
this, &ExampleSetModel::updateQtVersionList);
connect(ProjectExplorer::KitManager::instance(), &ProjectExplorer::KitManager::defaultkitChanged,
this, &ExampleSetModel::updateQtVersionList);
updateQtVersionList();
}
ExamplesListModelFilter::ExamplesListModelFilter(ExamplesListModel *sourceModel, bool showTutorialsOnly, QObject *parent) :
QSortFilterProxyModel(parent),
m_showTutorialsOnly(showTutorialsOnly)
{
setSourceModel(sourceModel);
setDynamicSortFilter(true);
setFilterCaseSensitivity(Qt::CaseInsensitive);
sort(0);
}
bool ExamplesListModelFilter::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
const ExampleItem item = sourceModel()->index(sourceRow, 0, sourceParent).data(
ExamplesListModel::ExampleItemRole).value<ExampleItem>();
if (m_showTutorialsOnly && item.type != Tutorial)
return false;
if (!m_showTutorialsOnly && item.type != Example && item.type != Demo)
return false;
if (!m_filterTags.isEmpty()) {
return Utils::allOf(m_filterTags, [&item](const QString &filterTag) {
return item.tags.contains(filterTag);
});
}
if (!m_filterStrings.isEmpty()) {
for (const QString &subString : m_filterStrings) {
bool wordMatch = false;
wordMatch |= bool(item.name.contains(subString, Qt::CaseInsensitive));
if (wordMatch)
continue;
const auto subMatch = [&subString](const QString &elem) { return elem.contains(subString); };
wordMatch |= Utils::contains(item.tags, subMatch);
if (wordMatch)
continue;
wordMatch |= bool(item.description.contains(subString, Qt::CaseInsensitive));
if (!wordMatch)
return false;
}
}
return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent);
}
void ExamplesListModelFilter::delayedUpdateFilter()
{
if (m_timerId != 0)
killTimer(m_timerId);
m_timerId = startTimer(320);
}
void ExamplesListModelFilter::timerEvent(QTimerEvent *timerEvent)
{
if (m_timerId == timerEvent->timerId()) {
invalidateFilter();
emit layoutChanged();
killTimer(m_timerId);
m_timerId = 0;
}
}
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())
, yychar(QLatin1Char(' ')) { }
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;
}
if (yychar == QLatin1Char('\\')) {
yyinp();
switch (yychar.unicode()) {
case '"': yytext += QLatin1Char('"'); yyinp(); break;
case '\'': yytext += QLatin1Char('\''); yyinp(); break;
case '\\': yytext += QLatin1Char('\\'); yyinp(); break;
}
} else {
yytext += yychar;
yyinp();
}
}
return STRING_LITERAL;
}
default:
if (ch.isLetterOrNumber() || ch == QLatin1Char('_')) {
yytext.clear();
yytext += ch;
while (yychar.isLetterOrNumber() || yychar == QLatin1Char('_')) {
yytext += yychar;
yyinp();
}
if (yychar == QLatin1Char(':') && yytext == QLatin1String("tag")) {
yyinp();
return TAG;
}
return STRING_LITERAL;
}
}
yytext += ch;
return UNKNOWN;
}
};
void ExamplesListModelFilter::setSearchString(const QString &arg)
{
if (m_searchString == arg)
return;
m_searchString = arg;
m_filterTags.clear();
m_filterStrings.clear();
// parse and update
SearchStringLexer lex(arg);
bool isTag = false;
while (int tk = lex()) {
if (tk == SearchStringLexer::TAG) {
isTag = true;
m_filterStrings.append(lex.yytext);
}
if (tk == SearchStringLexer::STRING_LITERAL) {
if (isTag) {
m_filterStrings.pop_back();
m_filterTags.append(lex.yytext);
isTag = false;
} else {
m_filterStrings.append(lex.yytext);
}
}
}
delayedUpdateFilter();
}
} // namespace Internal
} // namespace QtSupport