1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "addnodemodel.h"
5#include "nodesmodel.h"
6#include "effectmanager.h"
7#include <QDir>
8
9bool operator==(const AddNodeModel::NodeData &a, const AddNodeModel::NodeData &b) noexcept
10{
11 return a.name == b.name;
12}
13
14AddNodeModel::AddNodeModel(QObject *effectManager)
15 : QAbstractListModel(effectManager)
16{
17 m_effectManager = static_cast<EffectManager *>(effectManager);
18 connect(sender: this, signal: &QAbstractListModel::modelReset, context: this, slot: &AddNodeModel::rowCountChanged);
19 updateNodesList();
20}
21
22int AddNodeModel::rowCount(const QModelIndex &) const
23{
24 return m_modelList.size();
25}
26
27QHash<int, QByteArray> AddNodeModel::roleNames() const
28{
29 QHash<int, QByteArray> roles;
30 roles[Name] = "name";
31 roles[Description] = "description";
32 roles[File] = "file";
33 roles[Group] = "group";
34 roles[Properties] = "properties";
35 roles[CanBeAdded] = "canBeAdded";
36 roles[Show] = "show";
37 roles[RequiredNodes] = "requires";
38 return roles;
39}
40
41QVariant AddNodeModel::data(const QModelIndex &index, int role) const
42{
43 if (!index.isValid())
44 return QVariant();
45
46 if (index.row() >= m_modelList.size())
47 return false;
48
49 const auto &node = (m_modelList)[index.row()];
50
51 if (role == Name)
52 return QVariant::fromValue(value: node.name);
53 else if (role == Description)
54 return QVariant::fromValue(value: node.description);
55 else if (role == File)
56 return QVariant::fromValue(value: node.file);
57 else if (role == Group)
58 return QVariant::fromValue(value: node.group);
59 else if (role == Properties)
60 return QVariant::fromValue(value: node.properties);
61 else if (role == CanBeAdded)
62 return QVariant::fromValue(value: node.canBeAdded);
63 else if (role == Show)
64 return QVariant::fromValue(value: node.show);
65 else if (role == RequiredNodes)
66 return QVariant::fromValue(value: node.requiredNodes.join(sep: ", "));
67
68 return QVariant();
69}
70
71void AddNodeModel::loadNodesFromPath(const QString &path) {
72 qInfo() << "Loading nodes from:" << path;
73 QList<NodeData> nodes;
74
75 QDir rootDirectory(path);
76 QStringList dirList;
77 dirList << path;
78 // List subdirectories, in our preferred order
79 QStringList subDirList = rootDirectory.entryList(filters: QDir::AllDirs | QDir::NoDotAndDotDot, sort: QDir::Name);
80 for (auto &subDir : subDirList) {
81 QString dirPath = (path + "/" + subDir);
82 // Trick to get "common" nodes first in list
83 if (subDir == "common")
84 dirList.prepend(t: dirPath);
85 else
86 dirList.append(t: dirPath);
87 }
88 // Load nodes from all subdirectories
89 for (const auto &dirPath : dirList) {
90 QDir directory(dirPath);
91 QStringList nodeList = directory.entryList(nameFilters: QStringList() << "*.qen" << "*.QEN", filters: QDir::Files, sort: QDir::Name);
92 for (auto &filename : nodeList) {
93 QString filePath = directory.path() + "/" + filename;
94 auto node = m_effectManager->loadEffectNode(filename: filePath);
95 if (!node.name.isEmpty()) {
96 NodeData data;
97 data.file = filePath;
98 data.name = node.name;
99 if (!node.jsonUniforms.isEmpty()) {
100 for (const auto &u : node.jsonUniforms) {
101 NodeDataProperty property;
102 property.m_name = u.name;
103 if (u.type == UniformModel::Uniform::Type::Define)
104 property.m_type = QStringLiteral("define");
105 else
106 property.m_type = UniformModel::typeToProperty(type: u.type);
107 QVariant varProperty;
108 varProperty.setValue(property);
109 data.properties << varProperty;
110 }
111 }
112 data.description = node.description;
113 data.group = directory.dirName();
114 // Seek through code to get tags
115 QStringList shaderCodeLines;
116 shaderCodeLines += node.vertexCode.split(sep: '\n');
117 shaderCodeLines += node.fragmentCode.split(sep: '\n');
118 for (const auto &codeLine : shaderCodeLines) {
119 QString trimmedLine = codeLine.trimmed();
120 if (trimmedLine.startsWith(s: "@requires")) {
121 // Get the required node, remove "@requires"
122 QString l = trimmedLine.sliced(pos: 9).trimmed();
123 QString nodeName = l.split(sep: ' ').first();
124 if (!nodeName.isEmpty() && !data.requiredNodes.contains(str: nodeName))
125 data.requiredNodes << nodeName;
126 }
127 }
128 if (!m_modelList.contains(t: data))
129 nodes << data;
130 }
131 }
132 }
133
134 // Add all nodes into model
135 for (const auto &node : nodes)
136 m_modelList << node;
137}
138
139void AddNodeModel::updateCanBeAdded(const QStringList &propertyNames)
140{
141 beginResetModel();
142 // Check which nodes contain overlapping properties
143 for (auto &nodeData : m_modelList) {
144 for (const auto &variant : nodeData.properties) {
145 NodeDataProperty property = variant.value<NodeDataProperty>();
146 if (propertyNames.contains(str: property.m_name)) {
147 nodeData.canBeAdded = false;
148 } else {
149 nodeData.canBeAdded = true;
150 }
151 }
152 }
153 endResetModel();
154}
155
156void AddNodeModel::updateShowHide(const QString &groupName, bool show)
157{
158 int i = 0;
159 for (auto &nodeData : m_modelList) {
160 if (nodeData.group == groupName) {
161 nodeData.show = show;
162 Q_EMIT dataChanged(topLeft: QAbstractItemModel::createIndex(arow: 0, acolumn: 0),
163 bottomRight: QAbstractItemModel::createIndex(arow: i, acolumn: 0));
164 }
165 i++;
166 }
167}
168
169void AddNodeModel::updateNodesList()
170{
171 beginResetModel();
172
173 m_modelList.clear();
174
175 QString defaultNodePath = m_effectManager->settings()->defaultResourcePath() + "/defaultnodes";
176 loadNodesFromPath(path: defaultNodePath);
177
178 auto customNodesPaths = m_effectManager->settings()->customNodesPaths();
179 for (const auto &nodesPath : std::as_const(t&: customNodesPaths))
180 loadNodesFromPath(path: nodesPath);
181
182 static QString envCustomNodePath = qEnvironmentVariable(varName: "QQEM_CUSTOM_NODES_PATH");
183 if (!envCustomNodePath.isEmpty())
184 loadNodesFromPath(path: envCustomNodePath);
185
186 endResetModel();
187}
188

source code of qtquickeffectmaker/tools/qqem/addnodemodel.cpp