// 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 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); connect(this, &QAbstractListModel::modelReset, this, &AddNodeModel::rowCountChanged); updateNodesList(); } int AddNodeModel::rowCount(const QModelIndex &) const { return m_modelList.size(); } QHash AddNodeModel::roleNames() const { QHash 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 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(); 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(); }