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 ©File : 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