Revision control
Copy as Markdown
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
// Qt-Security score:critical reason:data-parser
#include "addnodemodel.h"
#include "nodesmodel.h"
#include "effectmanager.h"
#include <QDir>
bool operator==(const AddNodeModel::NodeData &a, const AddNodeModel::NodeData &b) noexcept
{
return a.name == b.name;
}
AddNodeModel::AddNodeModel(QObject *effectManager)
: QAbstractListModel(effectManager)
{
m_effectManager = static_cast<EffectManager *>(effectManager);
connect(this, &QAbstractListModel::modelReset, this, &AddNodeModel::rowCountChanged);
updateNodesList();
}
int AddNodeModel::rowCount(const QModelIndex &) const
{
return m_modelList.size();
}
QHash<int, QByteArray> AddNodeModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[Name] = "name";
roles[Description] = "description";
roles[File] = "file";
roles[Group] = "group";
roles[Properties] = "properties";
roles[CanBeAdded] = "canBeAdded";
roles[Show] = "show";
roles[RequiredNodes] = "requires";
return roles;
}
QVariant AddNodeModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (index.row() >= m_modelList.size())
return false;
const auto &node = (m_modelList)[index.row()];
if (role == Name)
return QVariant::fromValue(node.name);
else if (role == Description)
return QVariant::fromValue(node.description);
else if (role == File)
return QVariant::fromValue(node.file);
else if (role == Group)
return QVariant::fromValue(node.group);
else if (role == Properties)
return QVariant::fromValue(node.properties);
else if (role == CanBeAdded)
return QVariant::fromValue(node.canBeAdded);
else if (role == Show)
return QVariant::fromValue(node.show);
else if (role == RequiredNodes)
return QVariant::fromValue(node.requiredNodes.join(", "));
return QVariant();
}
void AddNodeModel::loadNodesFromPath(const QString &path) {
qInfo() << "Loading nodes from:" << path;
QList<NodeData> nodes;
QDir rootDirectory(path);
QStringList dirList;
dirList << path;
// List subdirectories, in our preferred order
QStringList subDirList = rootDirectory.entryList(QDir::AllDirs | QDir::NoDotAndDotDot, QDir::Name);
for (auto &subDir : subDirList) {
QString dirPath = (path + "/" + subDir);
// Trick to get "common" nodes first in list
if (subDir == "common")
dirList.prepend(dirPath);
else
dirList.append(dirPath);
}
// Load nodes from all subdirectories
for (const auto &dirPath : dirList) {
QDir directory(dirPath);
QStringList nodeList = directory.entryList(QStringList() << "*.qen" << "*.QEN", QDir::Files, QDir::Name);
for (auto &filename : nodeList) {
QString filePath = directory.path() + "/" + filename;
auto node = m_effectManager->loadEffectNode(filePath);
if (!node.name.isEmpty()) {
NodeData data;
data.file = filePath;
data.name = node.name;
if (!node.jsonUniforms.isEmpty()) {
for (const auto &u : node.jsonUniforms) {
NodeDataProperty property;
property.m_name = u.name;
if (u.type == UniformModel::Uniform::Type::Define)
property.m_type = QStringLiteral("define");
else
property.m_type = UniformModel::typeToProperty(u.type);
QVariant varProperty;
varProperty.setValue(property);
data.properties << varProperty;
}
}
data.description = node.description;
data.group = directory.dirName();
// Seek through code to get tags
QStringList shaderCodeLines;
shaderCodeLines += node.vertexCode.split('\n');
shaderCodeLines += node.fragmentCode.split('\n');
for (const auto &codeLine : shaderCodeLines) {
QString trimmedLine = codeLine.trimmed();
if (trimmedLine.startsWith("@requires")) {
// Get the required node, remove "@requires"
QString l = trimmedLine.sliced(9).trimmed();
QString nodeName = l.split(' ').first();
if (!nodeName.isEmpty() && !data.requiredNodes.contains(nodeName))
data.requiredNodes << nodeName;
}
}
if (!m_modelList.contains(data))
nodes << data;
}
}
}
// Add all nodes into model
for (const auto &node : nodes)
m_modelList << node;
}
void AddNodeModel::updateCanBeAdded(const QStringList &propertyNames)
{
beginResetModel();
// Check which nodes contain overlapping properties
for (auto &nodeData : m_modelList) {
for (const auto &variant : nodeData.properties) {
NodeDataProperty property = variant.value<NodeDataProperty>();
if (propertyNames.contains(property.m_name)) {
nodeData.canBeAdded = false;
} else {
nodeData.canBeAdded = true;
}
}
}
endResetModel();
}
void AddNodeModel::updateShowHide(const QString &groupName, bool show)
{
int i = 0;
for (auto &nodeData : m_modelList) {
if (nodeData.group == groupName) {
nodeData.show = show;
Q_EMIT dataChanged(QAbstractItemModel::createIndex(0, 0),
QAbstractItemModel::createIndex(i, 0));
}
i++;
}
}
void AddNodeModel::updateNodesList()
{
beginResetModel();
m_modelList.clear();
QString defaultNodePath = m_effectManager->settings()->defaultResourcePath() + "/defaultnodes";
loadNodesFromPath(defaultNodePath);
auto customNodesPaths = m_effectManager->settings()->customNodesPaths();
for (const auto &nodesPath : std::as_const(customNodesPaths))
loadNodesFromPath(nodesPath);
static QString envCustomNodePath = qEnvironmentVariable("QQEM_CUSTOM_NODES_PATH");
if (!envCustomNodePath.isEmpty())
loadNodesFromPath(envCustomNodePath);
endResetModel();
}