aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/qmldesigner/designercore/model/modelutils.cpp
blob: 6c3e1ea50f581a4fcd8b4531d9ce61690a839f82 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0

#include "modelutils.h"

#include <abstractview.h>
#include <nodeabstractproperty.h>
#include <nodemetainfo.h>
#include <projectstorage/projectstorage.h>
#include <projectstorage/sourcepathcache.h>

#include <coreplugin/messagebox.h>

#include <utils/expected.h>
#include <utils/ranges.h>

#include <algorithm>

namespace QmlDesigner::ModelUtils {

namespace {

enum class ImportError { EmptyImportName, HasAlreadyImport, NoModule };

::Utils::expected<Import, ImportError> findImport(const QString &importName,
                                                  const std::function<bool(const Import &)> &predicate,
                                                  const Imports &imports,
                                                  const Imports &modules)
{
    if (importName.isEmpty())
        return ::Utils::make_unexpected(ImportError::EmptyImportName);

    auto hasName = [&](const auto &import) {
        return import.url() == importName || import.file() == importName;
    };

    bool hasImport = std::any_of(imports.begin(), imports.end(), hasName);

    if (hasImport)
        return ::Utils::make_unexpected(ImportError::HasAlreadyImport);

    auto foundModule = std::find_if(modules.begin(), modules.end(), [&](const Import &import) {
        return hasName(import) && predicate(import);
    });

    if (foundModule == modules.end())
        return ::Utils::make_unexpected(ImportError::NoModule);

    return *foundModule;
}

} // namespace

bool addImportWithCheck(const QString &importName,
                        const std::function<bool(const Import &)> &predicate,
                        Model *model)
{
    return addImportsWithCheck({importName}, predicate, model);
}

bool addImportWithCheck(const QString &importName, Model *model)
{
    return addImportWithCheck(
        importName, [](const Import &) { return true; }, model);
}

bool addImportsWithCheck(const QStringList &importNames, Model *model)
{
    return addImportsWithCheck(
        importNames, [](const Import &) { return true; }, model);
}

bool addImportsWithCheck(const QStringList &importNames,
                         const std::function<bool(const Import &)> &predicate,
                         Model *model)
{
    const Imports &imports = model->imports();
    const Imports &modules = model->possibleImports();

    Imports importsToAdd;
    importsToAdd.reserve(importNames.size());

    for (const QString &importName : importNames) {
        auto import = findImport(importName, predicate, imports, modules);

        if (import) {
            importsToAdd.push_back(*import);
        } else {
            if (import.error() == ImportError::NoModule)
                return false;
            else
                continue;
        }
    }

    if (!importsToAdd.isEmpty())
        model->changeImports(std::move(importsToAdd), {});

    return true;
}

PropertyMetaInfo metainfo(const AbstractProperty &property)
{
    return metainfo(property.parentModelNode(), property.name());
}

PropertyMetaInfo metainfo(const ModelNode &node, const PropertyName &propertyName)
{
    return node.metaInfo().property(propertyName);
}

QString componentFilePath([[maybe_unused]] const PathCacheType &pathCache, const NodeMetaInfo &metaInfo)
{
#ifdef QDS_USE_PROJECTSTORAGE
    auto typeSourceId = metaInfo.sourceId();

    if (typeSourceId && metaInfo.isFileComponent()) {
        return pathCache.sourcePath(typeSourceId).toQString();
    }

    return {};
#else
    return metaInfo.componentFileName();
#endif
}

QString componentFilePath(const ModelNode &node)
{
    if (node) {
        const auto &pathCache = node.model()->pathCache();
        return ModelUtils::componentFilePath(pathCache, node.metaInfo());
    }

    return {};
}

QList<ModelNode> pruneChildren(const QList<ModelNode> &nodes)
{
    QList<ModelNode> forwardNodes;
    QList<ModelNode> backNodes;

    auto pushIfIsNotChild = [](QList<ModelNode> &container, const ModelNode &node) {
        bool hasAncestor = Utils::anyOf(container, [node](const ModelNode &testNode) -> bool {
            return testNode.isAncestorOf(node);
        });
        if (!hasAncestor)
            container.append(node);
    };

    for (const ModelNode &node : nodes | Utils::views::reverse) {
        if (node)
            pushIfIsNotChild(forwardNodes, node);
    }

    for (const ModelNode &node : forwardNodes | Utils::views::reverse)
        pushIfIsNotChild(backNodes, node);

    return backNodes;
}

QList<ModelNode> allModelNodesWithId(AbstractView *view)
{
    QTC_ASSERT(view->isAttached(), return {});
    return Utils::filtered(view->allModelNodes(),
                           [&](const ModelNode &node) { return node.hasId(); });
}

bool isThisOrAncestorLocked(const ModelNode &node)
{
    if (!node.isValid())
        return false;

    if (node.locked())
        return true;

    if (node.isRootNode() || !node.hasParentProperty())
        return false;

    return isThisOrAncestorLocked(node.parentProperty().parentModelNode());
}

/*!
 * \brief The lowest common ancestor node for node1 and node2. If one of the nodes (Node A) is
 * the ancestor of the other node, the return value is Node A and not the parent of Node A.
 * \param node1 First node
 * \param node2 Second node
 * \param depthOfLCA Depth of the return value
 * \param depthOfNode1 Depth of node1. Use this parameter for optimization
 * \param depthOfNode2 Depth of node2. Use this parameter for optimization
 */
namespace {
ModelNode lowestCommonAncestor(const ModelNode &node1,
                               const ModelNode &node2,
                               int &depthOfLCA,
                               const int &depthOfNode1 = -1,
                               const int &depthOfNode2 = -1)
{
    auto depthOfNode = [](const ModelNode &node) -> int {
        int depth = 0;
        ModelNode parentNode = node;
        while (parentNode && !parentNode.isRootNode()) {
            depth++;
            parentNode = parentNode.parentProperty().parentModelNode();
        }

        return depth;
    };

    if (node1 == node2) {
        depthOfLCA = (depthOfNode1 < 0) ? ((depthOfNode2 < 0) ? depthOfNode(node1) : depthOfNode2)
                                        : depthOfNode1;
        return node1;
    }

    if (node1.model() != node2.model()) {
        depthOfLCA = -1;
        return {};
    }

    if (node1.isRootNode()) {
        depthOfLCA = 0;
        return node1;
    }

    if (node2.isRootNode()) {
        depthOfLCA = 0;
        return node2;
    }

    ModelNode nodeLower = node1;
    ModelNode nodeHigher = node2;
    int depthLower = (depthOfNode1 < 0) ? depthOfNode(nodeLower) : depthOfNode1;
    int depthHigher = (depthOfNode2 < 0) ? depthOfNode(nodeHigher) : depthOfNode2;

    if (depthLower > depthHigher) {
        std::swap(depthLower, depthHigher);
        std::swap(nodeLower, nodeHigher);
    }

    int depthDiff = depthHigher - depthLower;
    while (depthDiff--)
        nodeHigher = nodeHigher.parentProperty().parentModelNode();

    while (nodeLower != nodeHigher) {
        nodeLower = nodeLower.parentProperty().parentModelNode();
        nodeHigher = nodeHigher.parentProperty().parentModelNode();
        --depthLower;
    }

    depthOfLCA = depthLower;
    return nodeLower;
}
} // namespace

/*!
 * \brief The lowest common node containing all nodes. If one of the nodes (Node A) is
 * the ancestor of the other nodes, the return value is Node A and not the parent of Node A.
 */
ModelNode lowestCommonAncestor(Utils::span<const ModelNode> nodes)
{
    if (nodes.empty())
        return {};

    ModelNode accumulatedNode = nodes.front();
    int accumulatedNodeDepth = -1;
    for (const ModelNode &node : nodes.subspan(1)) {
        accumulatedNode = lowestCommonAncestor(accumulatedNode,
                                               node,
                                               accumulatedNodeDepth,
                                               accumulatedNodeDepth);
        if (!accumulatedNode)
            return {};
    }

    return accumulatedNode;
}

} // namespace QmlDesigner::ModelUtils