1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25 
26 #include "navigatortreemodel.h"
27 #include "navigatorview.h"
28 #include "choosetexturepropertydialog.h"
29 #include "qmldesignerplugin.h"
30 #include "itemlibrarywidget.h"
31 
32 #include <bindingproperty.h>
33 #include <designersettings.h>
34 #include <nodeabstractproperty.h>
35 #include <nodehints.h>
36 #include <nodelistproperty.h>
37 #include <nodeproperty.h>
38 #include <variantproperty.h>
39 #include <metainfo.h>
40 #include <abstractview.h>
41 #include <invalididexception.h>
42 #include <rewritingexception.h>
43 #include <qmlitemnode.h>
44 #include <designeractionmanager.h>
45 #include <import.h>
46 
47 #include <coreplugin/icore.h>
48 
49 #include <utils/algorithm.h>
50 #include <utils/qtcassert.h>
51 #include <utils/utilsicons.h>
52 
53 #include <QMimeData>
54 #include <QMessageBox>
55 #include <QApplication>
56 #include <QPointF>
57 #include <QDir>
58 #include <QFileInfo>
59 #include <QFile>
60 #include <QPixmap>
61 #include <QTimer>
62 
63 #include <coreplugin/messagebox.h>
64 
65 #include <QtDebug>
66 
67 namespace QmlDesigner {
68 
modelNodesFromMimeData(const QMimeData * mineData,AbstractView * view)69 static QList<ModelNode> modelNodesFromMimeData(const QMimeData *mineData, AbstractView *view)
70 {
71     QByteArray encodedModelNodeData = mineData->data(QLatin1String("application/vnd.modelnode.list"));
72     QDataStream modelNodeStream(&encodedModelNodeData, QIODevice::ReadOnly);
73 
74     QList<ModelNode> modelNodeList;
75     while (!modelNodeStream.atEnd()) {
76         qint32 internalId;
77         modelNodeStream >> internalId;
78         if (view->hasModelNodeForInternalId(internalId))
79             modelNodeList.append(view->modelNodeForInternalId(internalId));
80     }
81 
82     return modelNodeList;
83 }
84 
fitsToTargetProperty(const NodeAbstractProperty & targetProperty,const QList<ModelNode> & modelNodeList)85 bool fitsToTargetProperty(const NodeAbstractProperty &targetProperty,
86                           const QList<ModelNode> &modelNodeList)
87 {
88     bool const canBeContainer =
89         NodeHints::fromModelNode(targetProperty.parentModelNode()).canBeContainerFor(modelNodeList.first());
90     return !(targetProperty.isNodeProperty() &&
91              modelNodeList.count() > 1) && canBeContainer;
92 }
93 
msgUnknownItem(const QString & t)94 static inline QString msgUnknownItem(const QString &t)
95 {
96     return NavigatorTreeModel::tr("Unknown component: %1").arg(t);
97 }
98 
removePosition(const ModelNode & node)99 static void removePosition(const ModelNode &node)
100 {
101     ModelNode modelNode = node;
102     if (modelNode.hasProperty("x"))
103         modelNode.removeProperty("x");
104     if (modelNode.hasProperty("y"))
105         modelNode.removeProperty("y");
106 }
107 
setScenePosition(const QmlDesigner::ModelNode & modelNode,const QPointF & positionInSceneSpace)108 static void setScenePosition(const QmlDesigner::ModelNode &modelNode,const QPointF &positionInSceneSpace)
109 {
110     if (modelNode.hasParentProperty() && QmlDesigner::QmlItemNode::isValidQmlItemNode(modelNode.parentProperty().parentModelNode())) {
111         QmlDesigner::QmlItemNode parentNode = modelNode.parentProperty().parentQmlObjectNode().toQmlItemNode();
112         QPointF positionInLocalSpace = parentNode.instanceSceneContentItemTransform().inverted().map(positionInSceneSpace);
113         modelNode.variantProperty("x").setValue(positionInLocalSpace.toPoint().x());
114         modelNode.variantProperty("y").setValue(positionInLocalSpace.toPoint().y());
115     }
116 }
117 
removeModelNodeFromNodeProperty(NodeAbstractProperty & parentProperty,const ModelNode & modelNode)118 static bool removeModelNodeFromNodeProperty(NodeAbstractProperty &parentProperty, const ModelNode &modelNode)
119 {
120     if (parentProperty.isNodeProperty()) {
121         bool removeNodeInPropertySucceeded = false;
122         ModelNode propertyNode = parentProperty.toNodeProperty().modelNode();
123         // Destruction of ancestors is not allowed
124         if (modelNode != propertyNode && !propertyNode.isAncestorOf(modelNode)) {
125             QApplication::setOverrideCursor(Qt::ArrowCursor);
126 
127             QMessageBox::StandardButton selectedButton = QMessageBox::warning(Core::ICore::dialogParent(),
128                                                                               QCoreApplication::translate("NavigatorTreeModel", "Warning"),
129                                                                               QCoreApplication::translate("NavigatorTreeModel","Reparenting the component %1 here will cause the "
130                                                                                                                                "component %2 to be deleted. Do you want to proceed?")
131                                                                               .arg(modelNode.id(), propertyNode.id()),
132                                                                               QMessageBox::Ok | QMessageBox::Cancel);
133             if (selectedButton == QMessageBox::Ok) {
134                 propertyNode.destroy();
135                 removeNodeInPropertySucceeded = true;
136             }
137 
138             QApplication::restoreOverrideCursor();
139         }
140 
141         return removeNodeInPropertySucceeded;
142     }
143 
144     return true;
145 }
146 
slideModelNodeInList(NodeAbstractProperty & parentProperty,const ModelNode & modelNode,int targetIndex)147 static void slideModelNodeInList(NodeAbstractProperty &parentProperty, const ModelNode &modelNode, int targetIndex)
148 {
149     if (parentProperty.isNodeListProperty()) {
150         int index = parentProperty.indexOf(modelNode);
151         if (index < targetIndex) { // item is first removed from oldIndex, then inserted at new index
152             --targetIndex;
153         }
154         if (index != targetIndex)
155             parentProperty.toNodeListProperty().slide(index, targetIndex);
156     }
157 }
158 
isInLayoutable(NodeAbstractProperty & parentProperty)159 static bool isInLayoutable(NodeAbstractProperty &parentProperty)
160 {
161     return parentProperty.isDefaultProperty() && parentProperty.parentModelNode().metaInfo().isLayoutable();
162 }
163 
reparentModelNodeToNodeProperty(NodeAbstractProperty & parentProperty,const ModelNode & modelNode)164 static void reparentModelNodeToNodeProperty(NodeAbstractProperty &parentProperty, const ModelNode &modelNode)
165 {
166     try {
167         if (!modelNode.hasParentProperty() || parentProperty != modelNode.parentProperty()) {
168             if (isInLayoutable(parentProperty)) {
169                 removePosition(modelNode);
170                 parentProperty.reparentHere(modelNode);
171             } else {
172                 if (QmlItemNode::isValidQmlItemNode(modelNode)) {
173                     QPointF scenePosition = QmlItemNode(modelNode).instanceScenePosition();
174                     parentProperty.reparentHere(modelNode);
175                     if (!scenePosition.isNull())
176                         setScenePosition(modelNode, scenePosition);
177                 } else {
178                     parentProperty.reparentHere(modelNode);
179                 }
180             }
181         }
182     }  catch (const RewritingException &exception) { //better safe than sorry! There always might be cases where we fail
183         exception.showException();
184     }
185 }
186 
NavigatorTreeModel(QObject * parent)187 NavigatorTreeModel::NavigatorTreeModel(QObject *parent) : QAbstractItemModel(parent)
188 {
189     m_actionManager = &QmlDesignerPlugin::instance()->viewManager().designerActionManager();
190 }
191 
192 NavigatorTreeModel::~NavigatorTreeModel() = default;
193 
data(const QModelIndex & index,int role) const194 QVariant NavigatorTreeModel::data(const QModelIndex &index, int role) const
195 {
196     if (!index.isValid())
197         return QVariant();
198 
199     const ModelNode modelNode = modelNodeForIndex(index);
200     const QmlObjectNode currentQmlObjectNode(modelNode);
201 
202     QTC_ASSERT(m_view, return QVariant());
203 
204     if (!modelNode.isValid())
205         return QVariant();
206 
207     if (role == ItemIsVisibleRole) // independent of column
208         return m_view->isNodeInvisible(modelNode) ? Qt::Unchecked : Qt::Checked;
209 
210     if (role == ItemOrAncestorLocked)
211         return ModelNode::isThisOrAncestorLocked(modelNode);
212 
213     if (role == ModelNodeRole)
214         return QVariant::fromValue<ModelNode>(modelNode);
215 
216     if (index.column() == ColumnType::Name) {
217         if (role == Qt::DisplayRole) {
218             return modelNode.displayName();
219         } else if (role == Qt::DecorationRole) {
220             if (currentQmlObjectNode.hasError())
221                 return Utils::Icons::WARNING.icon();
222 
223             return modelNode.typeIcon();
224 
225         } else if (role == Qt::ToolTipRole) {
226             if (currentQmlObjectNode.hasError()) {
227                 QString errorString = currentQmlObjectNode.error();
228                 if (DesignerSettings::getValue(DesignerSettingsKey::STANDALONE_MODE).toBool()
229                         && currentQmlObjectNode.isRootNode()) {
230                     errorString.append(QString("\n%1").arg(tr("Changing the setting \"%1\" might solve the issue.").arg(
231                                                                tr("Use QML emulation layer that is built with the selected Qt"))));
232                 }
233                 return errorString;
234             }
235 
236             if (modelNode.metaInfo().isValid()) {
237                 if (m_actionManager->hasModelNodePreviewHandler(modelNode))
238                     return {}; // Images have special tooltip popup, so suppress regular one
239                 else
240                     return modelNode.type();
241             } else {
242                 return msgUnknownItem(QString::fromUtf8(modelNode.type()));
243             }
244         } else if (role == ToolTipImageRole) {
245             if (currentQmlObjectNode.hasError()) // Error already shown on regular tooltip
246                 return {};
247             auto op = m_actionManager->modelNodePreviewOperation(modelNode);
248             if (op)
249                 return op(modelNode);
250         }
251     } else if (index.column() == ColumnType::Alias) { // export
252         if (role == Qt::CheckStateRole)
253             return currentQmlObjectNode.isAliasExported() ? Qt::Checked : Qt::Unchecked;
254         else if (role == Qt::ToolTipRole && !modelNodeForIndex(index).isRootNode())
255             return tr("Toggles whether this component is exported as an "
256                       "alias property of the root component.");
257     } else if (index.column() == ColumnType::Visibility) { // visible
258         if (role == Qt::CheckStateRole)
259             return m_view->isNodeInvisible(modelNode) ? Qt::Unchecked : Qt::Checked;
260         else if (role == Qt::ToolTipRole && !modelNodeForIndex(index).isRootNode())
261             return tr("Toggles the visibility of this component in the form editor.\n"
262                       "This is independent of the visibility property.");
263     } else if (index.column() == ColumnType::Lock) { // lock
264         if (role == Qt::CheckStateRole)
265             return modelNode.locked() ? Qt::Checked : Qt::Unchecked;
266         else if (role == Qt::ToolTipRole && !modelNodeForIndex(index).isRootNode())
267             return tr("Toggles whether this component is locked.\n"
268                       "Locked components cannot be modified or selected.");
269     }
270 
271     return QVariant();
272 }
273 
flags(const QModelIndex & index) const274 Qt::ItemFlags NavigatorTreeModel::flags(const QModelIndex &index) const
275 {
276     if (modelNodeForIndex(index).isRootNode()) {
277         Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDropEnabled;
278         if (index.column() == ColumnType::Name)
279             return flags | Qt::ItemIsEditable;
280         else
281             return flags;
282     }
283 
284     const ModelNode modelNode = modelNodeForIndex(index);
285 
286     if (index.column() == ColumnType::Alias
287         || index.column() == ColumnType::Visibility
288         || index.column() == ColumnType::Lock) {
289         if (ModelNode::isThisOrAncestorLocked(modelNode))
290             return Qt::ItemIsEnabled | Qt::ItemIsUserCheckable;
291         else
292             return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable;
293     }
294 
295     if (ModelNode::isThisOrAncestorLocked(modelNode))
296         return Qt::NoItemFlags;
297 
298     if (index.column() == ColumnType::Name)
299         return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsDropEnabled | Qt::ItemIsDragEnabled;
300 
301     return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable;
302 }
303 
appendForcedNodes(const NodeListProperty & property,QList<ModelNode> & list)304 void static appendForcedNodes(const NodeListProperty &property, QList<ModelNode> &list)
305 {
306     const QStringList visibleProperties = NodeHints::fromModelNode(property.parentModelNode()).visibleNonDefaultProperties();
307     for (const ModelNode &node : property.parentModelNode().directSubModelNodes()) {
308         if (!list.contains(node) && visibleProperties.contains(QString::fromUtf8(node.parentProperty().name())))
309             list.append(node);
310     }
311 }
312 
filteredList(const NodeListProperty & property,bool filter,bool reverseOrder)313 QList<ModelNode> filteredList(const NodeListProperty &property, bool filter, bool reverseOrder)
314 {
315     QList<ModelNode> list;
316 
317     if (filter) {
318         list.append(Utils::filtered(property.toModelNodeList(), [] (const ModelNode &arg) {
319             const char auxProp[] = "showInNavigator@Internal";
320             if (arg.hasAuxiliaryData(auxProp))
321                 return arg.auxiliaryData(auxProp).toBool();
322             const bool value = QmlItemNode::isValidQmlItemNode(arg) || NodeHints::fromModelNode(arg).visibleInNavigator();
323             arg.setAuxiliaryData(auxProp, value);
324             return value;
325         }));
326     } else {
327         list = property.toModelNodeList();
328     }
329 
330     appendForcedNodes(property, list);
331 
332     if (reverseOrder)
333         std::reverse(list.begin(), list.end());
334 
335     return list;
336 }
337 
index(int row,int column,const QModelIndex & parent) const338 QModelIndex NavigatorTreeModel::index(int row, int column,
339                                       const QModelIndex &parent) const
340 {
341     if (!m_view->model())
342         return {};
343 
344     if (!hasIndex(row, column, parent))
345         return QModelIndex();
346 
347     if (!parent.isValid())
348         return createIndexFromModelNode(0, column, m_view->rootModelNode());
349 
350     ModelNode parentModelNode = modelNodeForIndex(parent);
351 
352     ModelNode modelNode;
353     if (parentModelNode.defaultNodeListProperty().isValid())
354         modelNode = filteredList(parentModelNode.defaultNodeListProperty(),
355                                  m_showOnlyVisibleItems,
356                                  m_reverseItemOrder).at(row);
357 
358     if (!modelNode.isValid())
359         return QModelIndex();
360 
361     return createIndexFromModelNode(row, column, modelNode);
362 }
363 
headerData(int,Qt::Orientation,int) const364 QVariant NavigatorTreeModel::headerData(int, Qt::Orientation, int) const
365 {
366     return QVariant();
367 }
368 
parent(const QModelIndex & index) const369 QModelIndex NavigatorTreeModel::parent(const QModelIndex &index) const
370 {
371     if (!index.isValid())
372         return {};
373 
374     const ModelNode modelNode = modelNodeForIndex(index);
375 
376     if (!modelNode.isValid())
377         return QModelIndex();
378 
379     if (!modelNode.hasParentProperty())
380         return QModelIndex();
381 
382     const ModelNode parentModelNode = modelNode.parentProperty().parentModelNode();
383 
384     int row = 0;
385 
386     if (!parentModelNode.isRootNode() && parentModelNode.parentProperty().isNodeListProperty())
387         row = filteredList(parentModelNode.parentProperty().toNodeListProperty(),
388                            m_showOnlyVisibleItems,
389                            m_reverseItemOrder).indexOf(parentModelNode);
390 
391     return createIndexFromModelNode(row, 0, parentModelNode);
392 }
393 
rowCount(const QModelIndex & parent) const394 int NavigatorTreeModel::rowCount(const QModelIndex &parent) const
395 {
396     if (!m_view->isAttached())
397         return 0;
398 
399     if (parent.column() > 0)
400         return 0;
401     const ModelNode modelNode = modelNodeForIndex(parent);
402 
403     if (!modelNode.isValid())
404         return 1;
405 
406     int rows = 0;
407 
408     if (modelNode.defaultNodeListProperty().isValid())
409         rows = filteredList(modelNode.defaultNodeListProperty(),
410                             m_showOnlyVisibleItems,
411                             m_reverseItemOrder).count();
412 
413     return rows;
414 }
415 
columnCount(const QModelIndex & parent) const416 int NavigatorTreeModel::columnCount(const QModelIndex &parent) const
417 {
418     if (parent.column() > 0)
419         return 0;
420 
421     return ColumnType::Count;
422 }
423 
modelNodeForIndex(const QModelIndex & index) const424 ModelNode NavigatorTreeModel::modelNodeForIndex(const QModelIndex &index) const
425 {
426     if (!index.isValid())
427         return ModelNode();
428 
429     if (!m_view || !m_view->model())
430         return ModelNode();
431 
432     return m_view->modelNodeForInternalId(index.internalId());
433 }
434 
hasModelNodeForIndex(const QModelIndex & index) const435 bool NavigatorTreeModel::hasModelNodeForIndex(const QModelIndex &index) const
436 {
437     if (!index.isValid())
438         return false;
439 
440     return m_view->modelNodeForInternalId(index.internalId()).isValid();
441 }
442 
setView(NavigatorView * view)443 void NavigatorTreeModel::setView(NavigatorView *view)
444 {
445     m_view = view;
446 }
447 
mimeTypes() const448 QStringList NavigatorTreeModel::mimeTypes() const
449 {
450     const static QStringList types({"application/vnd.modelnode.list",
451                        "application/vnd.bauhaus.itemlibraryinfo",
452                        "application/vnd.bauhaus.libraryresource"});
453 
454     return types;
455 }
456 
mimeData(const QModelIndexList & modelIndexList) const457 QMimeData *NavigatorTreeModel::mimeData(const QModelIndexList &modelIndexList) const
458 {
459     auto mimeData = new QMimeData();
460 
461     QByteArray encodedModelNodeData;
462     QDataStream encodedModelNodeDataStream(&encodedModelNodeData, QIODevice::WriteOnly);
463     QSet<QModelIndex> rowAlreadyUsedSet;
464 
465     for (const QModelIndex &modelIndex : modelIndexList) {
466         if (modelIndex.isValid()) {
467             const QModelIndex idModelIndex = modelIndex.sibling(modelIndex.row(), 0);
468             if (!rowAlreadyUsedSet.contains(idModelIndex)) {
469                 rowAlreadyUsedSet.insert(idModelIndex);
470                 encodedModelNodeDataStream << idModelIndex.internalId();
471             }
472         }
473     }
474 
475     mimeData->setData("application/vnd.modelnode.list", encodedModelNodeData);
476 
477     return mimeData;
478 }
479 
indexForModelNode(const ModelNode & node) const480 QModelIndex NavigatorTreeModel::indexForModelNode(const ModelNode &node) const
481 {
482     return m_nodeIndexHash.value(node);
483 }
484 
createIndexFromModelNode(int row,int column,const ModelNode & modelNode) const485 QModelIndex NavigatorTreeModel::createIndexFromModelNode(int row, int column, const ModelNode &modelNode) const
486 {
487     QModelIndex index = createIndex(row, column, modelNode.internalId());
488     if (column == 0)
489         m_nodeIndexHash.insert(modelNode, index);
490 
491     return index;
492 }
493 
findTargetProperty(const QModelIndex & rowModelIndex,NavigatorTreeModel * navigatorTreeModel,NodeAbstractProperty * targetProperty,int * targetRowNumber,const PropertyName & propertyName={})494 static bool findTargetProperty(const QModelIndex &rowModelIndex,
495                                NavigatorTreeModel *navigatorTreeModel,
496                                NodeAbstractProperty *targetProperty,
497                                int *targetRowNumber,
498                                const PropertyName &propertyName = {})
499 {
500     QModelIndex targetItemIndex;
501     PropertyName targetPropertyName;
502 
503     if (*targetRowNumber < 0 || *targetRowNumber > navigatorTreeModel->rowCount(rowModelIndex))
504         *targetRowNumber = navigatorTreeModel->rowCount(rowModelIndex);
505 
506     if (navigatorTreeModel->hasModelNodeForIndex(rowModelIndex)) {
507         targetItemIndex = rowModelIndex;
508         const ModelNode targetNode = navigatorTreeModel->modelNodeForIndex(targetItemIndex);
509         if (!targetNode.metaInfo().hasDefaultProperty())
510             return false;
511 
512         if (propertyName.isEmpty() || !targetNode.metaInfo().hasProperty(propertyName))
513             targetPropertyName = targetNode.metaInfo().defaultPropertyName();
514         else
515             targetPropertyName = propertyName;
516     }
517 
518     // Disallow dropping items between properties, which are listed first.
519     if (*targetRowNumber < 0)
520         return false;
521 
522     const ModelNode targetNode(navigatorTreeModel->modelNodeForIndex(targetItemIndex));
523     *targetProperty = targetNode.nodeAbstractProperty(targetPropertyName);
524 
525     return true;
526 }
527 
dropMimeData(const QMimeData * mimeData,Qt::DropAction action,int rowNumber,int,const QModelIndex & dropModelIndex)528 bool NavigatorTreeModel::dropMimeData(const QMimeData *mimeData,
529                                       Qt::DropAction action,
530                                       int rowNumber,
531                                       int /*columnNumber*/,
532                                       const QModelIndex &dropModelIndex)
533 {
534     if (action == Qt::IgnoreAction)
535         return true;
536 
537     if (m_reverseItemOrder)
538         rowNumber = rowCount(dropModelIndex) - rowNumber;
539 
540     if (dropModelIndex.model() == this) {
541         if (mimeData->hasFormat("application/vnd.bauhaus.itemlibraryinfo")) {
542             handleItemLibraryItemDrop(mimeData, rowNumber, dropModelIndex);
543         } else if (mimeData->hasFormat("application/vnd.bauhaus.libraryresource")) {
544             const QStringList assetsPaths = QString::fromUtf8(mimeData->data("application/vnd.bauhaus.libraryresource")).split(",");
545             NodeAbstractProperty targetProperty;
546 
547             const QModelIndex rowModelIndex = dropModelIndex.sibling(dropModelIndex.row(), 0);
548             int targetRowNumber = rowNumber;
549             bool foundTarget = findTargetProperty(rowModelIndex, this, &targetProperty, &targetRowNumber);
550             if (foundTarget) {
551                 QList<ModelNode> addedNodes;
552                 ModelNode currNode;
553 
554                 QSet<QString> neededImports;
555                 for (const QString &assetPath : assetsPaths) {
556                     QString assetType = ItemLibraryWidget::getAssetTypeAndData(assetPath).first;
557                     if (assetType == "application/vnd.bauhaus.libraryresource.shader")
558                         neededImports.insert("QtQuick3D");
559                     else if (assetType == "application/vnd.bauhaus.libraryresource.sound")
560                         neededImports.insert("QtMultimedia");
561 
562                     if (neededImports.size() == 2)
563                         break;
564                 };
565 
566                 for (const QString &import : std::as_const(neededImports))
567                     addImport(import);
568 
569                 m_view->executeInTransaction("NavigatorTreeModel::dropMimeData", [&] {
570                     for (const QString &assetPath : assetsPaths) {
571                         auto assetTypeAndData = ItemLibraryWidget::getAssetTypeAndData(assetPath);
572                         QString assetType = assetTypeAndData.first;
573                         QString assetData = QString::fromUtf8(assetTypeAndData.second);
574                         if (assetType == "application/vnd.bauhaus.libraryresource.image")
575                             currNode = handleItemLibraryImageDrop(assetPath, targetProperty, rowModelIndex);
576                         else if (assetType == "application/vnd.bauhaus.libraryresource.font")
577                             currNode = handleItemLibraryFontDrop(assetData, targetProperty, rowModelIndex); // assetData is fontFamily
578                         else if (assetType == "application/vnd.bauhaus.libraryresource.shader")
579                             currNode = handleItemLibraryShaderDrop(assetPath, assetData == "f", targetProperty, rowModelIndex);
580                         else if (assetType == "application/vnd.bauhaus.libraryresource.sound")
581                             currNode = handleItemLibrarySoundDrop(assetPath, targetProperty, rowModelIndex);
582                         else if (assetType == "application/vnd.bauhaus.libraryresource.texture3d")
583                             currNode = handleItemLibraryTexture3dDrop(assetPath, targetProperty, rowModelIndex);
584 
585                         if (currNode.isValid())
586                             addedNodes.append(currNode);
587                     }
588                 });
589 
590                 if (!addedNodes.isEmpty()) {
591                     moveNodesInteractive(targetProperty, addedNodes, rowNumber);
592                     m_view->setSelectedModelNodes(addedNodes);
593                 }
594             }
595         } else if (mimeData->hasFormat("application/vnd.modelnode.list")) {
596             handleInternalDrop(mimeData, rowNumber, dropModelIndex);
597         }
598     }
599 
600     return false; // don't let the view do drag&drop on its own
601 }
602 
handleInternalDrop(const QMimeData * mimeData,int rowNumber,const QModelIndex & dropModelIndex)603 void NavigatorTreeModel::handleInternalDrop(const QMimeData *mimeData,
604                                             int rowNumber,
605                                             const QModelIndex &dropModelIndex)
606 {
607     QTC_ASSERT(m_view, return);
608     const QModelIndex rowModelIndex = dropModelIndex.sibling(dropModelIndex.row(), 0);
609     int targetRowNumber = rowNumber;
610     NodeAbstractProperty targetProperty;
611 
612     bool foundTarget = findTargetProperty(rowModelIndex, this, &targetProperty, &targetRowNumber);
613 
614     if (foundTarget) {
615         QList<ModelNode> modelNodeList = modelNodesFromMimeData(mimeData, m_view);
616 
617         if (fitsToTargetProperty(targetProperty, modelNodeList))
618             moveNodesInteractive(targetProperty, modelNodeList, targetRowNumber);
619     }
620 }
621 
createItemLibraryEntryFromMimeData(const QByteArray & data)622 static ItemLibraryEntry createItemLibraryEntryFromMimeData(const QByteArray &data)
623 {
624     QDataStream stream(data);
625 
626     ItemLibraryEntry itemLibraryEntry;
627     stream >> itemLibraryEntry;
628 
629     return itemLibraryEntry;
630 }
631 
handleItemLibraryItemDrop(const QMimeData * mimeData,int rowNumber,const QModelIndex & dropModelIndex)632 void NavigatorTreeModel::handleItemLibraryItemDrop(const QMimeData *mimeData, int rowNumber, const QModelIndex &dropModelIndex)
633 {
634     QTC_ASSERT(m_view, return);
635 
636     const QModelIndex rowModelIndex = dropModelIndex.sibling(dropModelIndex.row(), 0);
637     int targetRowNumber = rowNumber;
638     NodeAbstractProperty targetProperty;
639 
640     const ItemLibraryEntry itemLibraryEntry =
641         createItemLibraryEntryFromMimeData(mimeData->data("application/vnd.bauhaus.itemlibraryinfo"));
642 
643     const NodeHints hints = NodeHints::fromItemLibraryEntry(itemLibraryEntry);
644 
645     const QString targetPropertyName = hints.forceNonDefaultProperty();
646 
647     bool foundTarget = findTargetProperty(rowModelIndex, this, &targetProperty, &targetRowNumber, targetPropertyName.toUtf8());
648     bool moveNodesAfter = true;
649 
650     if (foundTarget) {
651         if (!hints.canBeDroppedInNavigator())
652             return;
653 
654         bool validContainer = false;
655         bool showMatToCompInfo = false;
656         QmlObjectNode newQmlObjectNode;
657         m_view->executeInTransaction("NavigatorTreeModel::handleItemLibraryItemDrop", [&] {
658             newQmlObjectNode = QmlItemNode::createQmlObjectNode(m_view, itemLibraryEntry, QPointF(), targetProperty, false);
659             ModelNode newModelNode = newQmlObjectNode.modelNode();
660             auto insertIntoList = [&](const QByteArray &listPropertyName, const ModelNode &targetNode) {
661                 if (targetNode.isValid()) {
662                     BindingProperty listProp = targetNode.bindingProperty(listPropertyName);
663                     if (listProp.isValid()) {
664                         QString expression = listProp.expression();
665                         int bracketIndex = expression.indexOf(']');
666                         if (expression.isEmpty())
667                             expression = newModelNode.validId();
668                         else if (bracketIndex == -1)
669                             expression = QStringLiteral("[%1,%2]").arg(expression).arg(newModelNode.validId());
670                         else
671                             expression.insert(bracketIndex, QStringLiteral(",%1").arg(newModelNode.validId()));
672                         listProp.setExpression(expression);
673                     }
674                 }
675             };
676             if (newModelNode.isValid()) {
677                 if (newModelNode.isSubclassOf("QtQuick3D.Effect")) {
678                     // Insert effects dropped to either View3D or SceneEnvironment into the
679                     // SceneEnvironment's effects list
680                     ModelNode targetEnv;
681                     if (targetProperty.parentModelNode().isSubclassOf("QtQuick3D.SceneEnvironment")) {
682                         targetEnv = targetProperty.parentModelNode();
683                         validContainer = true;
684                     } else if (targetProperty.parentModelNode().isSubclassOf("QtQuick3D.View3D")) {
685                         // see if View3D has environment set to it
686                         BindingProperty envNodeProp = targetProperty.parentModelNode().bindingProperty("environment");
687                         if (envNodeProp.isValid())  {
688                             ModelNode envNode = envNodeProp.resolveToModelNode();
689                             if (envNode.isValid())
690                                 targetEnv = envNode;
691                         }
692                         validContainer = true;
693                     }
694                     insertIntoList("effects", targetEnv);
695                 } else if (newModelNode.isSubclassOf("QtQuick3D.Material")) {
696                     if (targetProperty.parentModelNode().isSubclassOf("QtQuick3D.Model")) {
697                         // Insert material dropped to a model node into the materials list of the model
698                         ModelNode targetModel;
699                         targetModel = targetProperty.parentModelNode();
700                         insertIntoList("materials", targetModel);
701                         validContainer = true;
702                     } else if (targetProperty.parentModelNode().isSubclassOf("QtQuick3D.Node")
703                                && targetProperty.parentModelNode().isComponent()) {
704                         // Inserting materials under imported components is likely a mistake, so
705                         // notify user with a helpful messagebox that suggests the correct action.
706                         showMatToCompInfo = true;
707                     }
708                 } else {
709                     const bool isShader = newModelNode.isSubclassOf("QtQuick3D.Shader");
710                     if (isShader || newModelNode.isSubclassOf("QtQuick3D.Command")) {
711                         if (targetProperty.parentModelNode().isSubclassOf("QtQuick3D.Pass")) {
712                             // Shaders and commands inserted into a Pass will be added to proper list.
713                             // They are also moved to the same level as the pass, as passes don't
714                             // allow child nodes (QTBUG-86219).
715                             ModelNode targetModel;
716                             targetModel = targetProperty.parentModelNode();
717                             if (isShader)
718                                 insertIntoList("shaders", targetModel);
719                             else
720                                 insertIntoList("commands", targetModel);
721                             NodeAbstractProperty parentProp = targetProperty.parentProperty();
722                             if (parentProp.isValid()) {
723                                 targetProperty = parentProp;
724                                 targetModel = targetProperty.parentModelNode();
725                                 targetRowNumber = rowCount(indexForModelNode(targetModel));
726 
727                                 // Move node to new parent within the same transaction as we don't
728                                 // want undo to place the node under invalid parent
729                                 moveNodesAfter = false;
730                                 moveNodesInteractive(targetProperty, {newQmlObjectNode}, targetRowNumber, false);
731                                 validContainer = true;
732                             }
733                         }
734                     }
735                 }
736                 if (!validContainer) {
737                     if (!showMatToCompInfo)
738                         validContainer = NodeHints::fromModelNode(targetProperty.parentModelNode()).canBeContainerFor(newModelNode);
739                     if (!validContainer)
740                         newQmlObjectNode.destroy();
741                 }
742             }
743         });
744 
745         if (validContainer) {
746             if (moveNodesAfter && newQmlObjectNode.isValid() && targetProperty.isNodeListProperty()) {
747                 QList<ModelNode> newModelNodeList;
748                 newModelNodeList.append(newQmlObjectNode);
749 
750                 moveNodesInteractive(targetProperty, newModelNodeList, targetRowNumber);
751             }
752 
753             if (newQmlObjectNode.isValid())
754                 m_view->setSelectedModelNode(newQmlObjectNode.modelNode());
755         }
756 
757         const QStringList copyFiles = itemLibraryEntry.extraFilePaths();
758         if (!copyFiles.isEmpty()) {
759             // Files are copied into the same directory as the current qml document
760             for (const auto &copyFile : copyFiles) {
761                 QFileInfo fi(copyFile);
762                 const QString targetFile = DocumentManager::currentFilePath().toFileInfo().dir()
763                         .absoluteFilePath(fi.fileName());
764                 // We don't want to overwrite existing default files
765                 if (!QFileInfo::exists(targetFile)) {
766                     if (!QFile::copy(copyFile, targetFile))
767                         qWarning() << QStringLiteral("Copying extra file '%1' failed.").arg(copyFile);
768                 }
769             }
770         }
771 
772         if (showMatToCompInfo) {
773             QMessageBox::StandardButton selectedButton = QMessageBox::information(
774                         Core::ICore::dialogParent(),
775                         QCoreApplication::translate("NavigatorTreeModel", "Warning"),
776                         QCoreApplication::translate(
777                             "NavigatorTreeModel",
778                             "Inserting materials under imported 3D component nodes is not supported. "
779                             "Materials used in imported 3D components have to be modified inside the component itself.\n\n"
780                             "Would you like to go into component '%1'?")
781                         .arg(targetProperty.parentModelNode().id()),
782                         QMessageBox::Yes | QMessageBox::No,
783                         QMessageBox::No);
784             if (selectedButton == QMessageBox::Yes) {
785                 qint32 internalId = targetProperty.parentModelNode().internalId();
786                 QTimer::singleShot(0, this, [internalId, this]() {
787                     if (!m_view.isNull() && m_view->model()) {
788                         ModelNode node = m_view->modelNodeForInternalId(internalId);
789                         if (node.isValid() && node.isComponent())
790                             DocumentManager::goIntoComponent(node);
791                     }
792                 });
793             }
794         }
795     }
796 }
797 
handleItemLibraryImageDrop(const QString & imagePath,NodeAbstractProperty targetProperty,const QModelIndex & rowModelIndex)798 ModelNode NavigatorTreeModel::handleItemLibraryImageDrop(const QString &imagePath,
799                                                          NodeAbstractProperty targetProperty,
800                                                          const QModelIndex &rowModelIndex)
801 {
802     QTC_ASSERT(m_view, return {});
803 
804     ModelNode targetNode(modelNodeForIndex(rowModelIndex));
805 
806     const QString imagePathRelative = DocumentManager::currentFilePath().toFileInfo().dir().relativeFilePath(imagePath); // relative to .ui.qml file
807 
808     ModelNode newModelNode;
809 
810     if (!dropAsImage3dTexture(targetNode, targetProperty, imagePathRelative, newModelNode)) {
811         if (targetNode.isSubclassOf("QtQuick.Image")
812                 || targetNode.isSubclassOf("QtQuick.BorderImage")) {
813             // if dropping an image on an existing image, set the source
814             targetNode.variantProperty("source").setValue(imagePathRelative);
815         } else {
816             // create an image
817             QmlItemNode newItemNode = QmlItemNode::createQmlItemNodeFromImage(m_view, imagePath, QPointF(), targetProperty, false);
818             if (NodeHints::fromModelNode(targetProperty.parentModelNode()).canBeContainerFor(newItemNode.modelNode()))
819                 newModelNode = newItemNode.modelNode();
820             else
821                 newItemNode.destroy();
822         }
823     }
824 
825     return newModelNode;
826 }
827 
handleItemLibraryFontDrop(const QString & fontFamily,NodeAbstractProperty targetProperty,const QModelIndex & rowModelIndex)828 ModelNode NavigatorTreeModel::handleItemLibraryFontDrop(const QString &fontFamily,
829                                                         NodeAbstractProperty targetProperty,
830                                                         const QModelIndex &rowModelIndex)
831 {
832     QTC_ASSERT(m_view, return {});
833 
834     ModelNode targetNode(modelNodeForIndex(rowModelIndex));
835 
836     ModelNode newModelNode;
837 
838     if (targetNode.isSubclassOf("QtQuick.Text")) {
839         // if dropping into an existing Text, update font
840         targetNode.variantProperty("font.family").setValue(fontFamily);
841     } else {
842         // create a Text node
843         QmlItemNode newItemNode = QmlItemNode::createQmlItemNodeFromFont(
844                     m_view, fontFamily, QPointF(), targetProperty, false);
845         if (NodeHints::fromModelNode(targetProperty.parentModelNode()).canBeContainerFor(newItemNode.modelNode()))
846             newModelNode = newItemNode.modelNode();
847         else
848             newItemNode.destroy();
849     }
850 
851     return newModelNode;
852 }
853 
addImport(const QString & importName)854 void NavigatorTreeModel::addImport(const QString &importName)
855 {
856     Import import = Import::createLibraryImport(importName);
857     if (!m_view->model()->hasImport(import, true, true)) {
858         const QList<Import> possImports = m_view->model()->possibleImports();
859         for (const auto &possImport : possImports) {
860             if (possImport.url() == import.url()) {
861                 import = possImport;
862                 m_view->model()->changeImports({import}, {});
863                 QmlDesignerPlugin::instance()->currentDesignDocument()->updateSubcomponentManagerImport(import);
864                 break;
865             }
866         }
867     }
868 }
869 
handleItemLibraryShaderDrop(const QString & shaderPath,bool isFragShader,NodeAbstractProperty targetProperty,const QModelIndex & rowModelIndex)870 ModelNode NavigatorTreeModel::handleItemLibraryShaderDrop(const QString &shaderPath, bool isFragShader,
871                                                           NodeAbstractProperty targetProperty,
872                                                           const QModelIndex &rowModelIndex)
873 {
874     QTC_ASSERT(m_view, return {});
875 
876     ModelNode targetNode(modelNodeForIndex(rowModelIndex));
877     ModelNode newModelNode;
878 
879     const QString relPath = DocumentManager::currentFilePath().toFileInfo().dir().relativeFilePath(shaderPath);
880 
881     if (targetNode.isSubclassOf("QtQuick3D.Shader")) {
882         // if dropping into an existing Shader, update
883         targetNode.variantProperty("stage").setEnumeration(isFragShader ? "Shader.Fragment"
884                                                                         : "Shader.Vertex");
885         targetNode.variantProperty("shader").setValue(relPath);
886     } else {
887         // create a new Shader
888         ItemLibraryEntry itemLibraryEntry;
889         itemLibraryEntry.setName("Shader");
890         itemLibraryEntry.setType("QtQuick3D.Shader", 1, 0);
891 
892         // set shader properties
893         PropertyName prop = "shader";
894         QString type = "QByteArray";
895         QVariant val = relPath;
896         itemLibraryEntry.addProperty(prop, type, val);
897         prop = "stage";
898         type = "enum";
899         val = isFragShader ? "Shader.Fragment" : "Shader.Vertex";
900         itemLibraryEntry.addProperty(prop, type, val);
901 
902         // create a texture
903         newModelNode = QmlItemNode::createQmlObjectNode(m_view, itemLibraryEntry, {},
904                                                         targetProperty, false);
905 
906         // Rename the node based on shader source
907         QFileInfo fi(relPath);
908         newModelNode.setIdWithoutRefactoring(m_view->generateNewId(fi.baseName(),
909                                                                    "shader"));
910     }
911 
912     return newModelNode;
913 }
914 
handleItemLibrarySoundDrop(const QString & soundPath,NodeAbstractProperty targetProperty,const QModelIndex & rowModelIndex)915 ModelNode NavigatorTreeModel::handleItemLibrarySoundDrop(const QString &soundPath,
916                                                          NodeAbstractProperty targetProperty,
917                                                          const QModelIndex &rowModelIndex)
918 {
919     QTC_ASSERT(m_view, return {});
920 
921     ModelNode targetNode(modelNodeForIndex(rowModelIndex));
922     ModelNode newModelNode;
923 
924     const QString relPath = DocumentManager::currentFilePath().toFileInfo().dir().relativeFilePath(soundPath);
925 
926     if (targetNode.isSubclassOf("QtMultimedia.SoundEffect")) {
927         // if dropping into on an existing SoundEffect, update
928         targetNode.variantProperty("source").setValue(relPath);
929     } else {
930         // create a new SoundEffect
931         ItemLibraryEntry itemLibraryEntry;
932         itemLibraryEntry.setName("SoundEffect");
933         itemLibraryEntry.setType("QtMultimedia.SoundEffect", 1, 0);
934 
935         // set source property
936         PropertyName prop = "source";
937         QString type = "QUrl";
938         QVariant val = relPath;
939         itemLibraryEntry.addProperty(prop, type, val);
940 
941         // create a texture
942         newModelNode = QmlItemNode::createQmlObjectNode(m_view, itemLibraryEntry, {},
943                                                         targetProperty, false);
944 
945         // Rename the node based on source
946         QFileInfo fi(relPath);
947         newModelNode.setIdWithoutRefactoring(m_view->generateNewId(fi.baseName(),
948                                                                    "soundEffect"));
949         }
950 
951     return newModelNode;
952 }
953 
handleItemLibraryTexture3dDrop(const QString & tex3DPath,NodeAbstractProperty targetProperty,const QModelIndex & rowModelIndex)954 ModelNode NavigatorTreeModel::handleItemLibraryTexture3dDrop(const QString &tex3DPath,
955                                                              NodeAbstractProperty targetProperty,
956                                                              const QModelIndex &rowModelIndex)
957 {
958     QTC_ASSERT(m_view, return {});
959 
960     Import import = Import::createLibraryImport(QStringLiteral("QtQuick3D"));
961     if (!m_view->model()->hasImport(import, true, true))
962         return {};
963 
964     ModelNode targetNode(modelNodeForIndex(rowModelIndex));
965 
966     const QString imagePath = DocumentManager::currentFilePath().toFileInfo().dir()
967             .relativeFilePath(tex3DPath); // relative to qml file
968 
969     ModelNode newModelNode;
970 
971     if (!dropAsImage3dTexture(targetNode, targetProperty, imagePath, newModelNode)) {
972         m_view->executeInTransaction("NavigatorTreeModel::handleItemLibraryTexture3dDrop", [&] {
973             // create a standalone Texture3D at drop location
974             newModelNode = createTextureNode(targetProperty, imagePath);
975             if (!NodeHints::fromModelNode(targetProperty.parentModelNode()).canBeContainerFor(newModelNode))
976                 newModelNode.destroy();
977         });
978     }
979 
980     return newModelNode;
981 }
982 
dropAsImage3dTexture(const ModelNode & targetNode,const NodeAbstractProperty & targetProp,const QString & imagePath,ModelNode & newNode)983 bool NavigatorTreeModel::dropAsImage3dTexture(const ModelNode &targetNode,
984                                               const NodeAbstractProperty &targetProp,
985                                               const QString &imagePath,
986                                               ModelNode &newNode)
987 {
988     if (targetNode.isSubclassOf("QtQuick3D.Material")) {
989         // if dropping an image on a default material, create a texture instead of image
990         ChooseTexturePropertyDialog *dialog = nullptr;
991         if (targetNode.isSubclassOf("QtQuick3D.DefaultMaterial") || targetNode.isSubclassOf("QtQuick3D.PrincipledMaterial")) {
992             // Show texture property selection dialog
993             dialog = new ChooseTexturePropertyDialog(targetNode, Core::ICore::dialogParent());
994             dialog->exec();
995         }
996         if (!dialog || dialog->result() == QDialog::Accepted) {
997             m_view->executeInTransaction("NavigatorTreeModel::dropAsImage3dTexture", [&] {
998                 newNode = createTextureNode(targetProp, imagePath);
999                 if (newNode.isValid() && dialog) {
1000                     // Automatically set the texture to selected property
1001                     targetNode.bindingProperty(dialog->selectedProperty()).setExpression(newNode.validId());
1002                 }
1003             });
1004         }
1005         delete dialog;
1006         return newNode.isValid();
1007     } else if (targetNode.isSubclassOf("QtQuick3D.TextureInput")) {
1008         // If dropping an image on a TextureInput, create a texture on the same level as
1009         // TextureInput, as the TextureInput doesn't support Texture children (QTBUG-86219)
1010         m_view->executeInTransaction("NavigatorTreeModel::dropAsImage3dTexture", [&] {
1011             NodeAbstractProperty parentProp = targetProp.parentProperty();
1012             newNode = createTextureNode(parentProp, imagePath);
1013             if (newNode.isValid()) {
1014                 // Automatically set the texture to texture property
1015                 targetNode.bindingProperty("texture").setExpression(newNode.validId());
1016             }
1017         });
1018         return newNode.isValid();
1019     } else if (targetNode.isSubclassOf("QtQuick3D.Texture")) {
1020         // if dropping an image on an existing texture, set the source
1021         targetNode.variantProperty("source").setValue(imagePath);
1022         return true;
1023     }
1024 
1025     return false;
1026 }
1027 
createTextureNode(const NodeAbstractProperty & targetProp,const QString & imagePath)1028 ModelNode NavigatorTreeModel::createTextureNode(const NodeAbstractProperty &targetProp,
1029                                                 const QString &imagePath)
1030 {
1031     if (targetProp.isValid()) {
1032         // create a texture item lib
1033         ItemLibraryEntry itemLibraryEntry;
1034         itemLibraryEntry.setName("Texture");
1035         itemLibraryEntry.setType("QtQuick3D.Texture", 1, 0);
1036 
1037         // set texture source
1038         PropertyName prop = "source";
1039         QString type = "QUrl";
1040         QVariant val = imagePath;
1041         itemLibraryEntry.addProperty(prop, type, val);
1042 
1043         // create a texture
1044         ModelNode newModelNode = QmlItemNode::createQmlObjectNode(m_view, itemLibraryEntry, {},
1045                                                                   targetProp, false);
1046 
1047         // Rename the node based on source image
1048         QFileInfo fi(imagePath);
1049         newModelNode.setIdWithoutRefactoring(m_view->generateNewId(fi.baseName(), "textureImage"));
1050         return newModelNode;
1051     }
1052     return {};
1053 }
1054 
propertyType(const NodeAbstractProperty & property)1055 TypeName propertyType(const NodeAbstractProperty &property)
1056 {
1057     return property.parentModelNode().metaInfo().propertyTypeName(property.name());
1058 }
1059 
moveNodesInteractive(NodeAbstractProperty & parentProperty,const QList<ModelNode> & modelNodes,int targetIndex,bool executeInTransaction)1060 void NavigatorTreeModel::moveNodesInteractive(NodeAbstractProperty &parentProperty,
1061                                               const QList<ModelNode> &modelNodes,
1062                                               int targetIndex,
1063                                               bool executeInTransaction)
1064 {
1065     QTC_ASSERT(m_view, return);
1066 
1067     auto doMoveNodesInteractive = [&parentProperty, modelNodes, targetIndex](){
1068         const TypeName propertyQmlType = propertyType(parentProperty);
1069         int idx = targetIndex;
1070         for (const ModelNode &modelNode : modelNodes) {
1071             if (modelNode.isValid()
1072                     && modelNode != parentProperty.parentModelNode()
1073                     && !modelNode.isAncestorOf(parentProperty.parentModelNode())
1074                     && (modelNode.metaInfo().isSubclassOf(propertyQmlType) || propertyQmlType == "alias")) {
1075                 //### todo: allowing alias is just a heuristic
1076                 //once the MetaInfo is part of instances we can do this right
1077 
1078                 bool nodeCanBeMovedToParentProperty = removeModelNodeFromNodeProperty(parentProperty, modelNode);
1079                 if (nodeCanBeMovedToParentProperty) {
1080                     reparentModelNodeToNodeProperty(parentProperty, modelNode);
1081                     if (targetIndex > 0)
1082                         slideModelNodeInList(parentProperty, modelNode, idx++);
1083                 }
1084             }
1085         }
1086     };
1087 
1088     if (executeInTransaction)
1089         m_view->executeInTransaction("NavigatorTreeModel::moveNodesInteractive", doMoveNodesInteractive);
1090     else
1091         doMoveNodesInteractive();
1092 }
1093 
supportedDropActions() const1094 Qt::DropActions NavigatorTreeModel::supportedDropActions() const
1095 {
1096     return Qt::LinkAction | Qt::MoveAction;
1097 }
1098 
supportedDragActions() const1099 Qt::DropActions NavigatorTreeModel::supportedDragActions() const
1100 {
1101     return Qt::LinkAction;
1102 }
1103 
setData(const QModelIndex & index,const QVariant & value,int role)1104 bool NavigatorTreeModel::setData(const QModelIndex &index, const QVariant &value, int role)
1105 {
1106     ModelNode modelNode = modelNodeForIndex(index);
1107     if (index.column() == ColumnType::Alias && role == Qt::CheckStateRole) {
1108         QTC_ASSERT(m_view, return false);
1109         m_view->handleChangedExport(modelNode, value.toInt() != 0);
1110     } else if (index.column() == ColumnType::Visibility && role == Qt::CheckStateRole) {
1111         QmlVisualNode(modelNode).setVisibilityOverride(value.toInt() == 0);
1112     } else if (index.column() == ColumnType::Lock && role == Qt::CheckStateRole) {
1113         modelNode.setLocked(value.toInt() != 0);
1114     }
1115 
1116     return true;
1117 }
1118 
notifyDataChanged(const ModelNode & modelNode)1119 void NavigatorTreeModel::notifyDataChanged(const ModelNode &modelNode)
1120 {
1121     const QModelIndex index = indexForModelNode(modelNode);
1122     const QAbstractItemModel *model = index.model();
1123     const QModelIndex sibling = model ? model->sibling(index.row(), ColumnType::Count - 1, index) : QModelIndex();
1124     emit dataChanged(index, sibling);
1125 }
1126 
collectParents(const QList<ModelNode> & modelNodes)1127 static QList<ModelNode> collectParents(const QList<ModelNode> &modelNodes)
1128 {
1129     QSet<ModelNode> parents;
1130     for (const ModelNode &modelNode : modelNodes) {
1131         if (modelNode.isValid() && modelNode.hasParentProperty()) {
1132             const ModelNode parent = modelNode.parentProperty().parentModelNode();
1133             parents.insert(parent);
1134         }
1135     }
1136 
1137     return Utils::toList(parents);
1138 }
1139 
nodesToPersistentIndex(const QList<ModelNode> & modelNodes)1140 QList<QPersistentModelIndex> NavigatorTreeModel::nodesToPersistentIndex(const QList<ModelNode> &modelNodes)
1141 {
1142     return Utils::transform(modelNodes, [this](const ModelNode &modelNode) {
1143         return QPersistentModelIndex(indexForModelNode(modelNode));
1144     });
1145 }
1146 
notifyModelNodesRemoved(const QList<ModelNode> & modelNodes)1147 void NavigatorTreeModel::notifyModelNodesRemoved(const QList<ModelNode> &modelNodes)
1148 {
1149     QList<QPersistentModelIndex> indexes = nodesToPersistentIndex(collectParents(modelNodes));
1150     emit layoutAboutToBeChanged(indexes);
1151     emit layoutChanged(indexes);
1152 }
1153 
notifyModelNodesInserted(const QList<ModelNode> & modelNodes)1154 void NavigatorTreeModel::notifyModelNodesInserted(const QList<ModelNode> &modelNodes)
1155 {
1156     QList<QPersistentModelIndex> indexes = nodesToPersistentIndex(collectParents(modelNodes));
1157     emit layoutAboutToBeChanged(indexes);
1158     emit layoutChanged(indexes);
1159 }
1160 
notifyModelNodesMoved(const QList<ModelNode> & modelNodes)1161 void NavigatorTreeModel::notifyModelNodesMoved(const QList<ModelNode> &modelNodes)
1162 {
1163     QList<QPersistentModelIndex> indexes = nodesToPersistentIndex(collectParents(modelNodes));
1164     emit layoutAboutToBeChanged(indexes);
1165     emit layoutChanged(indexes);
1166 }
1167 
notifyIconsChanged()1168 void NavigatorTreeModel::notifyIconsChanged()
1169 {
1170     emit dataChanged(index(0, 0), index(rowCount(), 0), {Qt::DecorationRole});
1171 }
1172 
setFilter(bool showOnlyVisibleItems)1173 void NavigatorTreeModel::setFilter(bool showOnlyVisibleItems)
1174 {
1175     m_showOnlyVisibleItems = showOnlyVisibleItems;
1176     resetModel();
1177 }
1178 
setOrder(bool reverseItemOrder)1179 void NavigatorTreeModel::setOrder(bool reverseItemOrder)
1180 {
1181     m_reverseItemOrder = reverseItemOrder;
1182     resetModel();
1183 }
1184 
resetModel()1185 void NavigatorTreeModel::resetModel()
1186 {
1187     beginResetModel();
1188     m_nodeIndexHash.clear();
1189     endResetModel();
1190 }
1191 
updateToolTipPixmap(const ModelNode & node,const QPixmap & pixmap)1192 void NavigatorTreeModel::updateToolTipPixmap(const ModelNode &node, const QPixmap &pixmap)
1193 {
1194     emit toolTipPixmapUpdated(node.id(), pixmap);
1195 }
1196 
1197 } // QmlDesigner
1198