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 "navigatorview.h"
27 #include "navigatortreemodel.h"
28 #include "navigatorwidget.h"
29 #include "qmldesignerconstants.h"
30 #include "qmldesignericons.h"
31 
32 #include "nameitemdelegate.h"
33 #include "iconcheckboxitemdelegate.h"
34 
35 #include <bindingproperty.h>
36 #include <designmodecontext.h>
37 #include <designersettings.h>
38 #include <nodeproperty.h>
39 #include <nodelistproperty.h>
40 #include <variantproperty.h>
41 #include <qmlitemnode.h>
42 #include <rewritingexception.h>
43 #include <nodeinstanceview.h>
44 #include <theme.h>
45 
46 #include <coreplugin/editormanager/editormanager.h>
47 #include <coreplugin/icore.h>
48 
49 #include <projectexplorer/project.h>
50 #include <projectexplorer/session.h>
51 #include <projectexplorer/projectnodes.h>
52 
53 #include <utils/algorithm.h>
54 #include <utils/icon.h>
55 #include <utils/utilsicons.h>
56 #include <utils/stylehelper.h>
57 
58 #include <QHeaderView>
59 #include <QTimer>
60 #include <QPixmap>
61 
setScenePos(const QmlDesigner::ModelNode & modelNode,const QPointF & pos)62 static inline void setScenePos(const QmlDesigner::ModelNode &modelNode,const QPointF &pos)
63 {
64     if (modelNode.hasParentProperty() && QmlDesigner::QmlItemNode::isValidQmlItemNode(modelNode.parentProperty().parentModelNode())) {
65         QmlDesigner::QmlItemNode parentNode = modelNode.parentProperty().parentQmlObjectNode().toQmlItemNode();
66 
67         if (!parentNode.modelNode().metaInfo().isLayoutable()) {
68             QPointF localPos = parentNode.instanceSceneTransform().inverted().map(pos);
69             modelNode.variantProperty("x").setValue(localPos.toPoint().x());
70             modelNode.variantProperty("y").setValue(localPos.toPoint().y());
71         } else { //Items in Layouts do not have a position
72             modelNode.removeProperty("x");
73             modelNode.removeProperty("y");
74         }
75     }
76 }
77 
moveNodesUp(const QList<QmlDesigner::ModelNode> & nodes)78 static inline void moveNodesUp(const QList<QmlDesigner::ModelNode> &nodes)
79 {
80     for (const auto &node : nodes) {
81         if (!node.isRootNode() && node.parentProperty().isNodeListProperty()) {
82             int oldIndex = node.parentProperty().indexOf(node);
83             int index = oldIndex;
84             index--;
85             if (index < 0)
86                 index = node.parentProperty().count() - 1; //wrap around
87             if (oldIndex != index)
88                 node.parentProperty().toNodeListProperty().slide(oldIndex, index);
89         }
90     }
91 }
92 
moveNodesDown(const QList<QmlDesigner::ModelNode> & nodes)93 static inline void moveNodesDown(const QList<QmlDesigner::ModelNode> &nodes)
94 {
95     for (const auto &node : nodes) {
96         if (!node.isRootNode() && node.parentProperty().isNodeListProperty()) {
97             int oldIndex = node.parentProperty().indexOf(node);
98             int index = oldIndex;
99             index++;
100             if (index >= node.parentProperty().count())
101                 index = 0; //wrap around
102             if (oldIndex != index)
103                 node.parentProperty().toNodeListProperty().slide(oldIndex, index);
104         }
105     }
106 }
107 
108 namespace QmlDesigner {
109 
NavigatorView(QObject * parent)110 NavigatorView::NavigatorView(QObject* parent) :
111     AbstractView(parent),
112     m_blockSelectionChangedSignal(false)
113 {
114 
115 }
116 
~NavigatorView()117 NavigatorView::~NavigatorView()
118 {
119     if (m_widget && !m_widget->parent())
120         delete m_widget.data();
121 }
122 
hasWidget() const123 bool NavigatorView::hasWidget() const
124 {
125     return true;
126 }
127 
widgetInfo()128 WidgetInfo NavigatorView::widgetInfo()
129 {
130     if (!m_widget)
131         setupWidget();
132 
133     return createWidgetInfo(m_widget.data(),
134                             new WidgetInfo::ToolBarWidgetDefaultFactory<NavigatorWidget>(m_widget.data()),
135                             QStringLiteral("Navigator"),
136                             WidgetInfo::LeftPane,
137                             0,
138                             tr("Navigator"));
139 }
140 
modelAttached(Model * model)141 void NavigatorView::modelAttached(Model *model)
142 {
143     AbstractView::modelAttached(model);
144 
145     QTreeView *treeView = treeWidget();
146 
147     treeView->header()->setSectionResizeMode(NavigatorTreeModel::ColumnType::Name, QHeaderView::Stretch);
148     treeView->header()->resizeSection(NavigatorTreeModel::ColumnType::Alias, 26);
149     treeView->header()->resizeSection(NavigatorTreeModel::ColumnType::Visibility, 26);
150     treeView->header()->resizeSection(NavigatorTreeModel::ColumnType::Lock, 26);
151     treeView->setIndentation(20);
152 
153     m_currentModelInterface->setFilter(false);
154 
155     QTimer::singleShot(0, this, [this, treeView]() {
156         m_currentModelInterface->setFilter(
157                     DesignerSettings::getValue(DesignerSettingsKey::NAVIGATOR_SHOW_ONLY_VISIBLE_ITEMS).toBool());
158 
159         m_currentModelInterface->setOrder(
160                     DesignerSettings::getValue(DesignerSettingsKey::NAVIGATOR_REVERSE_ITEM_ORDER).toBool());
161 
162         // Expand everything to begin with to ensure model node to index cache is populated
163         treeView->expandAll();
164 
165         if (AbstractView::model() && m_expandMap.contains(AbstractView::model()->fileUrl())) {
166             const QHash<QString, bool> localExpandMap = m_expandMap[AbstractView::model()->fileUrl()];
167             auto it = localExpandMap.constBegin();
168             while (it != localExpandMap.constEnd()) {
169                 const QModelIndex index = indexForModelNode(modelNodeForId(it.key()));
170                 if (index.isValid())
171                     treeWidget()->setExpanded(index, it.value());
172                 ++it;
173             }
174         }
175     });
176 
177     clearExplorerWarnings();
178 }
179 
clearExplorerWarnings()180 void NavigatorView::clearExplorerWarnings()
181 {
182     QList<ModelNode> allNodes;
183     addNodeAndSubModelNodesToList(rootModelNode(), allNodes);
184     for (ModelNode node : allNodes) {
185         if (node.metaInfo().isFileComponent()) {
186             const ProjectExplorer::FileNode *fnode = fileNodeForModelNode(node);
187             if (fnode)
188                 fnode->setHasError(false);
189         }
190     }
191 }
192 
addNodeAndSubModelNodesToList(const ModelNode & node,QList<ModelNode> & nodes)193 void NavigatorView::addNodeAndSubModelNodesToList(const ModelNode &node, QList<ModelNode> &nodes)
194 {
195     nodes.append(node);
196     for (ModelNode subNode : node.allSubModelNodes()) {
197         addNodeAndSubModelNodesToList(subNode, nodes);
198     }
199 }
200 
modelAboutToBeDetached(Model * model)201 void NavigatorView::modelAboutToBeDetached(Model *model)
202 {
203     m_expandMap.remove(model->fileUrl());
204 
205     if (currentModel()) {
206         // Store expand state of the navigator tree
207         QHash<QString, bool> localExpandMap;
208         const ModelNode rootNode = rootModelNode();
209         const QModelIndex rootIndex = indexForModelNode(rootNode);
210 
211         std::function<void(const QModelIndex &)> gatherExpandedState;
212         gatherExpandedState = [&](const QModelIndex &index) {
213             if (index.isValid()) {
214                 const int rowCount = currentModel()->rowCount(index);
215                 for (int i = 0; i < rowCount; ++i) {
216                     const QModelIndex childIndex = currentModel()->index(i, 0, index);
217                     const ModelNode node = modelNodeForIndex(childIndex);
218                     // Just store collapsed states as everything is expanded by default
219                     if (node.isValid() && !treeWidget()->isExpanded(childIndex))
220                         localExpandMap.insert(node.id(), false);
221                     gatherExpandedState(childIndex);
222                 }
223             }
224         };
225         gatherExpandedState(rootIndex);
226         m_expandMap[model->fileUrl()] = localExpandMap;
227     }
228 
229     AbstractView::modelAboutToBeDetached(model);
230 }
231 
importsChanged(const QList<Import> &,const QList<Import> &)232 void NavigatorView::importsChanged(const QList<Import> &/*addedImports*/, const QList<Import> &/*removedImports*/)
233 {
234     treeWidget()->update();
235 }
236 
bindingPropertiesChanged(const QList<BindingProperty> & propertyList,PropertyChangeFlags)237 void NavigatorView::bindingPropertiesChanged(const QList<BindingProperty> & propertyList, PropertyChangeFlags /*propertyChange*/)
238 {
239     for (const BindingProperty &bindingProperty : propertyList) {
240         /* If a binding property that exports an item using an alias property has
241          * changed, we have to update the affected item.
242          */
243 
244         if (bindingProperty.isAliasExport())
245             m_currentModelInterface->notifyDataChanged(modelNodeForId(bindingProperty.expression()));
246     }
247 }
248 
customNotification(const AbstractView * view,const QString & identifier,const QList<ModelNode> & nodeList,const QList<QVariant> & data)249 void NavigatorView::customNotification(const AbstractView *view, const QString &identifier,
250                                        const QList<ModelNode> &nodeList, const QList<QVariant> &data)
251 {
252     Q_UNUSED(view)
253     Q_UNUSED(nodeList)
254     Q_UNUSED(data)
255 
256     if (identifier == "asset_import_update")
257         m_currentModelInterface->notifyIconsChanged();
258 }
259 
handleChangedExport(const ModelNode & modelNode,bool exported)260 void NavigatorView::handleChangedExport(const ModelNode &modelNode, bool exported)
261 {
262     const ModelNode rootNode = rootModelNode();
263     Q_ASSERT(rootNode.isValid());
264     const PropertyName modelNodeId = modelNode.id().toUtf8();
265     if (rootNode.hasProperty(modelNodeId))
266         rootNode.removeProperty(modelNodeId);
267     if (exported) {
268         executeInTransaction("NavigatorTreeModel:exportItem", [modelNode](){
269             QmlObjectNode qmlObjectNode(modelNode);
270             qmlObjectNode.ensureAliasExport();
271         });
272     }
273 }
274 
isNodeInvisible(const ModelNode & modelNode) const275 bool NavigatorView::isNodeInvisible(const ModelNode &modelNode) const
276 {
277     return QmlVisualNode(modelNode).visibilityOverride();
278 }
279 
disableWidget()280 void NavigatorView::disableWidget()
281 {
282     if (m_widget)
283         m_widget->disableNavigator();
284 }
285 
enableWidget()286 void NavigatorView::enableWidget()
287 {
288     if (m_widget)
289         m_widget->enableNavigator();
290 }
291 
modelNodePreviewPixmapChanged(const ModelNode & node,const QPixmap & pixmap)292 void NavigatorView::modelNodePreviewPixmapChanged(const ModelNode &node, const QPixmap &pixmap)
293 {
294     m_treeModel->updateToolTipPixmap(node, pixmap);
295 }
296 
modelNodeForIndex(const QModelIndex & modelIndex) const297 ModelNode NavigatorView::modelNodeForIndex(const QModelIndex &modelIndex) const
298 {
299     return modelIndex.model()->data(modelIndex, ModelNodeRole).value<ModelNode>();
300 }
301 
nodeAboutToBeRemoved(const ModelNode &)302 void NavigatorView::nodeAboutToBeRemoved(const ModelNode & /*removedNode*/)
303 {
304 }
305 
nodeRemoved(const ModelNode & removedNode,const NodeAbstractProperty &,AbstractView::PropertyChangeFlags)306 void NavigatorView::nodeRemoved(const ModelNode &removedNode,
307                                 const NodeAbstractProperty & /*parentProperty*/,
308                                 AbstractView::PropertyChangeFlags /*propertyChange*/)
309 {
310     m_currentModelInterface->notifyModelNodesRemoved({removedNode});
311 }
312 
nodeReparented(const ModelNode & modelNode,const NodeAbstractProperty &,const NodeAbstractProperty & oldPropertyParent,AbstractView::PropertyChangeFlags)313 void NavigatorView::nodeReparented(const ModelNode &modelNode,
314                                    const NodeAbstractProperty & /*newPropertyParent*/,
315                                    const NodeAbstractProperty & oldPropertyParent,
316                                    AbstractView::PropertyChangeFlags /*propertyChange*/)
317 {
318     if (!oldPropertyParent.isValid())
319         m_currentModelInterface->notifyModelNodesInserted({modelNode});
320     else
321         m_currentModelInterface->notifyModelNodesMoved({modelNode});
322     treeWidget()->expand(indexForModelNode(modelNode));
323 
324     // make sure selection is in sync again
325     QTimer::singleShot(0, this, &NavigatorView::updateItemSelection);
326 }
327 
nodeIdChanged(const ModelNode & modelNode,const QString &,const QString &)328 void NavigatorView::nodeIdChanged(const ModelNode& modelNode, const QString & /*newId*/, const QString & /*oldId*/)
329 {
330     m_currentModelInterface->notifyDataChanged(modelNode);
331 }
332 
propertiesAboutToBeRemoved(const QList<AbstractProperty> &)333 void NavigatorView::propertiesAboutToBeRemoved(const QList<AbstractProperty> &/*propertyList*/)
334 {
335 }
336 
propertiesRemoved(const QList<AbstractProperty> & propertyList)337 void NavigatorView::propertiesRemoved(const QList<AbstractProperty> &propertyList)
338 {
339     QList<ModelNode> modelNodes;
340     for (const AbstractProperty &property : propertyList) {
341         if (property.isNodeAbstractProperty()) {
342             NodeAbstractProperty nodeAbstractProperty(property.toNodeListProperty());
343             modelNodes.append(nodeAbstractProperty.directSubNodes());
344         }
345     }
346 
347     m_currentModelInterface->notifyModelNodesRemoved(modelNodes);
348 }
349 
rootNodeTypeChanged(const QString &,int,int)350 void NavigatorView::rootNodeTypeChanged(const QString &/*type*/, int /*majorVersion*/, int /*minorVersion*/)
351 {
352     m_currentModelInterface->notifyDataChanged(rootModelNode());
353 }
354 
nodeTypeChanged(const ModelNode & modelNode,const TypeName &,int,int)355 void NavigatorView::nodeTypeChanged(const ModelNode &modelNode, const TypeName &, int , int)
356 {
357     m_currentModelInterface->notifyDataChanged(modelNode);
358 }
359 
auxiliaryDataChanged(const ModelNode & modelNode,const PropertyName & name,const QVariant & data)360 void NavigatorView::auxiliaryDataChanged(const ModelNode &modelNode,
361                                          const PropertyName &name,
362                                          const QVariant &data)
363 {
364     Q_UNUSED(name)
365     Q_UNUSED(data)
366 
367     m_currentModelInterface->notifyDataChanged(modelNode);
368 }
369 
instanceErrorChanged(const QVector<ModelNode> & errorNodeList)370 void NavigatorView::instanceErrorChanged(const QVector<ModelNode> &errorNodeList)
371 {
372     for (const ModelNode &modelNode : errorNodeList) {
373         m_currentModelInterface->notifyDataChanged(modelNode);
374         propagateInstanceErrorToExplorer(modelNode);
375     }
376 }
377 
nodeOrderChanged(const NodeListProperty & listProperty)378 void NavigatorView::nodeOrderChanged(const NodeListProperty &listProperty)
379 {
380     m_currentModelInterface->notifyModelNodesMoved(listProperty.directSubNodes());
381 
382     // make sure selection is in sync again
383     QTimer::singleShot(0, this, &NavigatorView::updateItemSelection);
384 }
385 
changeToComponent(const QModelIndex & index)386 void NavigatorView::changeToComponent(const QModelIndex &index)
387 {
388     if (index.isValid() && currentModel()->data(index, Qt::UserRole).isValid()) {
389         const ModelNode doubleClickNode = modelNodeForIndex(index);
390         if (doubleClickNode.metaInfo().isFileComponent())
391             Core::EditorManager::openEditor(doubleClickNode.metaInfo().componentFileName(),
392                                             Utils::Id(), Core::EditorManager::DoNotMakeVisible);
393     }
394 }
395 
indexForModelNode(const ModelNode & modelNode) const396 QModelIndex NavigatorView::indexForModelNode(const ModelNode &modelNode) const
397 {
398     return m_currentModelInterface->indexForModelNode(modelNode);
399 }
400 
currentModel() const401 QAbstractItemModel *NavigatorView::currentModel() const
402 {
403     return treeWidget()->model();
404 }
405 
fileNodeForModelNode(const ModelNode & node) const406 const ProjectExplorer::FileNode *NavigatorView::fileNodeForModelNode(const ModelNode &node) const
407 {
408     QString filename = node.metaInfo().componentFileName();
409     Utils::FilePath filePath = Utils::FilePath::fromString(filename);
410     ProjectExplorer::Project *currentProject = ProjectExplorer::SessionManager::projectForFile(
411         filePath);
412 
413     if (!currentProject) {
414         filePath = Utils::FilePath::fromString(node.model()->fileUrl().toLocalFile());
415 
416         /* If the component does not belong to the project then we can fallback to the current file */
417         currentProject = ProjectExplorer::SessionManager::projectForFile(filePath);
418     }
419     if (!currentProject)
420         return nullptr;
421 
422     return currentProject->nodeForFilePath(filePath)->asFileNode();
423 }
424 
fileNodeForIndex(const QModelIndex & index) const425 const ProjectExplorer::FileNode *NavigatorView::fileNodeForIndex(const QModelIndex &index) const
426 {
427     if (index.isValid() && currentModel()->data(index, Qt::UserRole).isValid()) {
428         ModelNode node = modelNodeForIndex(index);
429         if (node.metaInfo().isFileComponent()) {
430             return fileNodeForModelNode(node);
431         }
432     }
433 
434     return nullptr;
435 }
436 
propagateInstanceErrorToExplorer(const ModelNode & modelNode)437 void NavigatorView::propagateInstanceErrorToExplorer(const ModelNode &modelNode) {
438     QModelIndex index = indexForModelNode(modelNode);;
439 
440     do {
441         const ProjectExplorer::FileNode *fnode = fileNodeForIndex(index);
442         if (fnode) {
443             fnode->setHasError(true);
444             return;
445         }
446         else {
447             index = index.parent();
448         }
449     } while (index.isValid());
450 }
451 
leftButtonClicked()452 void NavigatorView::leftButtonClicked()
453 {
454     if (selectedModelNodes().count() > 1)
455         return; //Semantics are unclear for multi selection.
456 
457     bool blocked = blockSelectionChangedSignal(true);
458 
459     for (const ModelNode &node : selectedModelNodes()) {
460         if (!node.isRootNode() && !node.parentProperty().parentModelNode().isRootNode()) {
461             if (QmlItemNode::isValidQmlItemNode(node)) {
462                 QPointF scenePos = QmlItemNode(node).instanceScenePosition();
463                 reparentAndCatch(node.parentProperty().parentProperty(), node);
464                 if (!scenePos.isNull())
465                     setScenePos(node, scenePos);
466             } else {
467                 reparentAndCatch(node.parentProperty().parentProperty(), node);
468             }
469         }
470     }
471 
472     updateItemSelection();
473     blockSelectionChangedSignal(blocked);
474 }
475 
rightButtonClicked()476 void NavigatorView::rightButtonClicked()
477 {
478     if (selectedModelNodes().count() > 1)
479         return; //Semantics are unclear for multi selection.
480 
481     bool blocked = blockSelectionChangedSignal(true);
482     bool reverse = DesignerSettings::getValue(DesignerSettingsKey::NAVIGATOR_REVERSE_ITEM_ORDER).toBool();
483 
484     for (const ModelNode &node : selectedModelNodes()) {
485         if (!node.isRootNode() && node.parentProperty().isNodeListProperty() && node.parentProperty().count() > 1) {
486             int index = node.parentProperty().indexOf(node);
487 
488             bool indexOk = false;
489 
490             if (reverse) {
491                 index++;
492                 indexOk = (index < node.parentProperty().count());
493             } else {
494                 index--;
495                 indexOk = (index >= 0);
496             }
497 
498             if (indexOk) { //for the first node the semantics are not clear enough. Wrapping would be irritating.
499                 ModelNode newParent = node.parentProperty().toNodeListProperty().at(index);
500 
501                 if (QmlItemNode::isValidQmlItemNode(node)
502                         && QmlItemNode::isValidQmlItemNode(newParent)
503                         && !newParent.metaInfo().defaultPropertyIsComponent()) {
504                     QPointF scenePos = QmlItemNode(node).instanceScenePosition();
505                     reparentAndCatch(newParent.nodeAbstractProperty(newParent.metaInfo().defaultPropertyName()), node);
506                     if (!scenePos.isNull())
507                         setScenePos(node, scenePos);
508                 } else {
509                     if (newParent.metaInfo().isValid() && !newParent.metaInfo().defaultPropertyIsComponent())
510                         reparentAndCatch(newParent.nodeAbstractProperty(newParent.metaInfo().defaultPropertyName()), node);
511                 }
512             }
513         }
514     }
515     updateItemSelection();
516     blockSelectionChangedSignal(blocked);
517 }
518 
upButtonClicked()519 void NavigatorView::upButtonClicked()
520 {
521     bool blocked = blockSelectionChangedSignal(true);
522     bool reverse = DesignerSettings::getValue(DesignerSettingsKey::NAVIGATOR_REVERSE_ITEM_ORDER).toBool();
523 
524     if (reverse)
525         moveNodesDown(selectedModelNodes());
526     else
527         moveNodesUp(selectedModelNodes());
528 
529     updateItemSelection();
530     blockSelectionChangedSignal(blocked);
531 }
532 
downButtonClicked()533 void NavigatorView::downButtonClicked()
534 {
535     bool blocked = blockSelectionChangedSignal(true);
536     bool reverse = DesignerSettings::getValue(DesignerSettingsKey::NAVIGATOR_REVERSE_ITEM_ORDER).toBool();
537 
538     if (reverse)
539         moveNodesUp(selectedModelNodes());
540     else
541         moveNodesDown(selectedModelNodes());
542 
543     updateItemSelection();
544     blockSelectionChangedSignal(blocked);
545 }
546 
filterToggled(bool flag)547 void NavigatorView::filterToggled(bool flag)
548 {
549     m_currentModelInterface->setFilter(flag);
550     treeWidget()->expandAll();
551     DesignerSettings::setValue(DesignerSettingsKey::NAVIGATOR_SHOW_ONLY_VISIBLE_ITEMS, flag);
552 }
553 
reverseOrderToggled(bool flag)554 void NavigatorView::reverseOrderToggled(bool flag)
555 {
556     m_currentModelInterface->setOrder(flag);
557     treeWidget()->expandAll();
558     DesignerSettings::setValue(DesignerSettingsKey::NAVIGATOR_REVERSE_ITEM_ORDER, flag);
559 }
560 
changeSelection(const QItemSelection &,const QItemSelection &)561 void NavigatorView::changeSelection(const QItemSelection & /*newSelection*/, const QItemSelection &/*deselected*/)
562 {
563     if (m_blockSelectionChangedSignal)
564         return;
565 
566     QSet<ModelNode> nodeSet;
567 
568     for (const QModelIndex &index : treeWidget()->selectionModel()->selectedIndexes()) {
569         const ModelNode modelNode = modelNodeForIndex(index);
570         if (modelNode.isValid())
571             nodeSet.insert(modelNode);
572     }
573 
574     bool blocked = blockSelectionChangedSignal(true);
575     setSelectedModelNodes(Utils::toList(nodeSet));
576     blockSelectionChangedSignal(blocked);
577 }
578 
selectedNodesChanged(const QList<ModelNode> &,const QList<ModelNode> &)579 void NavigatorView::selectedNodesChanged(const QList<ModelNode> &/*selectedNodeList*/, const QList<ModelNode> &/*lastSelectedNodeList*/)
580 {
581     // Update selection asynchronously to ensure NavigatorTreeModel's index cache is up to date
582     QTimer::singleShot(0, this, &NavigatorView::updateItemSelection);
583 }
584 
updateItemSelection()585 void NavigatorView::updateItemSelection()
586 {
587     if (!isAttached())
588         return;
589 
590     QItemSelection itemSelection;
591     for (const ModelNode &node : selectedModelNodes()) {
592         const QModelIndex index = indexForModelNode(node);
593 
594         if (index.isValid()) {
595             const QModelIndex beginIndex(currentModel()->index(index.row(), 0, index.parent()));
596             const QModelIndex endIndex(currentModel()->index(index.row(), currentModel()->columnCount(index.parent()) - 1, index.parent()));
597             if (beginIndex.isValid() && endIndex.isValid())
598                 itemSelection.select(beginIndex, endIndex);
599         } else {
600             // if the node index is invalid expand ancestors manually if they are valid.
601             ModelNode parentNode = node;
602             while (parentNode.hasParentProperty()) {
603                 parentNode = parentNode.parentProperty().parentQmlObjectNode();
604                 QModelIndex parentIndex = indexForModelNode(parentNode);
605                 if (parentIndex.isValid())
606                     treeWidget()->expand(parentIndex);
607                 else
608                     break;
609             }
610          }
611     }
612 
613     bool blocked = blockSelectionChangedSignal(true);
614     treeWidget()->selectionModel()->select(itemSelection, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
615     blockSelectionChangedSignal(blocked);
616 
617     if (!selectedModelNodes().isEmpty())
618         treeWidget()->scrollTo(indexForModelNode(selectedModelNodes().constFirst()));
619 
620     // make sure selected nodes are visible
621     for (const QModelIndex &selectedIndex : itemSelection.indexes()) {
622         if (selectedIndex.column() == 0)
623             expandAncestors(selectedIndex);
624     }
625 }
626 
treeWidget() const627 QTreeView *NavigatorView::treeWidget() const
628 {
629     if (m_widget)
630         return m_widget->treeView();
631     return nullptr;
632 }
633 
treeModel()634 NavigatorTreeModel *NavigatorView::treeModel()
635 {
636     return m_treeModel.data();
637 }
638 
639 // along the lines of QObject::blockSignals
blockSelectionChangedSignal(bool block)640 bool NavigatorView::blockSelectionChangedSignal(bool block)
641 {
642     bool oldValue = m_blockSelectionChangedSignal;
643     m_blockSelectionChangedSignal = block;
644     return oldValue;
645 }
646 
expandAncestors(const QModelIndex & index)647 void NavigatorView::expandAncestors(const QModelIndex &index)
648 {
649     QModelIndex currentIndex = index.parent();
650     while (currentIndex.isValid()) {
651         if (!treeWidget()->isExpanded(currentIndex))
652             treeWidget()->expand(currentIndex);
653         currentIndex = currentIndex.parent();
654     }
655 }
656 
reparentAndCatch(NodeAbstractProperty property,const ModelNode & modelNode)657 void NavigatorView::reparentAndCatch(NodeAbstractProperty property, const ModelNode &modelNode)
658 {
659     try {
660         property.reparentHere(modelNode);
661     } catch (Exception &exception) {
662         exception.showException();
663     }
664 }
665 
setupWidget()666 void NavigatorView::setupWidget()
667 {
668     m_widget = new NavigatorWidget(this);
669     m_treeModel = new NavigatorTreeModel(this);
670 
671 #ifndef QMLDESIGNER_TEST
672     auto navigatorContext = new Internal::NavigatorContext(m_widget.data());
673     Core::ICore::addContextObject(navigatorContext);
674 #endif
675 
676     m_treeModel->setView(this);
677     m_widget->setTreeModel(m_treeModel.data());
678     m_currentModelInterface = m_treeModel;
679 
680     connect(treeWidget()->selectionModel(), &QItemSelectionModel::selectionChanged, this, &NavigatorView::changeSelection);
681 
682     connect(m_widget.data(), &NavigatorWidget::leftButtonClicked, this, &NavigatorView::leftButtonClicked);
683     connect(m_widget.data(), &NavigatorWidget::rightButtonClicked, this, &NavigatorView::rightButtonClicked);
684     connect(m_widget.data(), &NavigatorWidget::downButtonClicked, this, &NavigatorView::downButtonClicked);
685     connect(m_widget.data(), &NavigatorWidget::upButtonClicked, this, &NavigatorView::upButtonClicked);
686     connect(m_widget.data(), &NavigatorWidget::filterToggled, this, &NavigatorView::filterToggled);
687     connect(m_widget.data(), &NavigatorWidget::reverseOrderToggled, this, &NavigatorView::reverseOrderToggled);
688 
689 #ifndef QMLDESIGNER_TEST
690     const QString fontName = "qtds_propertyIconFont.ttf";
691     const QSize size = QSize(28, 28);
692 
693     const QString visibilityOnUnicode = Theme::getIconUnicode(Theme::Icon::visibilityOn);
694     const QString visibilityOffUnicode = Theme::getIconUnicode(Theme::Icon::visibilityOff);
695 
696     const QString aliasOnUnicode = Theme::getIconUnicode(Theme::Icon::alias);
697     const QString aliasOffUnicode = aliasOnUnicode;
698 
699     const QString lockOnUnicode = Theme::getIconUnicode(Theme::Icon::lockOn);
700     const QString lockOffUnicode = Theme::getIconUnicode(Theme::Icon::lockOff);
701 
702     auto visibilityIconOffNormal = Utils::StyleHelper::IconFontHelper(
703         visibilityOffUnicode, Theme::getColor(Theme::DSnavigatorIcon), size, QIcon::Normal, QIcon::Off);
704     auto visibilityIconOffHover = Utils::StyleHelper::IconFontHelper(
705         visibilityOffUnicode, Theme::getColor(Theme::DSnavigatorIconHover), size, QIcon::Active, QIcon::Off);
706     auto visibilityIconOffSelected = Utils::StyleHelper::IconFontHelper(
707         visibilityOffUnicode, Theme::getColor(Theme::DSnavigatorIconSelected), size, QIcon::Selected, QIcon::Off);
708     auto visibilityIconOnNormal = Utils::StyleHelper::IconFontHelper(
709         visibilityOnUnicode, Theme::getColor(Theme::DSnavigatorIcon), size, QIcon::Normal, QIcon::On);
710     auto visibilityIconOnHover = Utils::StyleHelper::IconFontHelper(
711         visibilityOnUnicode, Theme::getColor(Theme::DSnavigatorIconHover), size, QIcon::Active, QIcon::On);
712     auto visibilityIconOnSelected = Utils::StyleHelper::IconFontHelper(
713         visibilityOnUnicode, Theme::getColor(Theme::DSnavigatorIconSelected), size, QIcon::Selected, QIcon::On);
714 
715     const QIcon visibilityIcon = Utils::StyleHelper::getIconFromIconFont(
716                 fontName, {visibilityIconOffNormal,
717                            visibilityIconOffHover,
718                            visibilityIconOffSelected,
719                            visibilityIconOnNormal,
720                            visibilityIconOnHover,
721                            visibilityIconOnSelected});
722 
723     auto aliasIconOffNormal = Utils::StyleHelper::IconFontHelper(
724         aliasOffUnicode, Theme::getColor(Theme::DSnavigatorIcon), size, QIcon::Normal, QIcon::Off);
725     auto aliasIconOffHover = Utils::StyleHelper::IconFontHelper(
726         aliasOffUnicode, Theme::getColor(Theme::DSnavigatorIconHover), size, QIcon::Active, QIcon::Off);
727     auto aliasIconOffSelected = Utils::StyleHelper::IconFontHelper(
728         aliasOffUnicode, Theme::getColor(Theme::DSnavigatorIconSelected), size, QIcon::Selected, QIcon::Off);
729     auto aliasIconOnNormal = Utils::StyleHelper::IconFontHelper(
730         aliasOnUnicode, Theme::getColor(Theme::DSnavigatorAliasIconChecked), size, QIcon::Normal, QIcon::On);
731     auto aliasIconOnHover = Utils::StyleHelper::IconFontHelper(
732         aliasOnUnicode, Theme::getColor(Theme::DSnavigatorAliasIconChecked), size, QIcon::Active, QIcon::On);
733     auto aliasIconOnSelected = Utils::StyleHelper::IconFontHelper(
734         aliasOnUnicode, Theme::getColor(Theme::DSnavigatorAliasIconChecked), size, QIcon::Selected, QIcon::On);
735 
736     const QIcon aliasIcon = Utils::StyleHelper::getIconFromIconFont(
737                 fontName, {aliasIconOffNormal,
738                            aliasIconOffHover,
739                            aliasIconOffSelected,
740                            aliasIconOnNormal,
741                            aliasIconOnHover,
742                            aliasIconOnSelected});
743 
744     auto lockIconOffNormal = Utils::StyleHelper::IconFontHelper(
745         lockOffUnicode, Theme::getColor(Theme::DSnavigatorIcon), size, QIcon::Normal, QIcon::Off);
746     auto lockIconOffHover = Utils::StyleHelper::IconFontHelper(
747         lockOffUnicode, Theme::getColor(Theme::DSnavigatorIconHover), size, QIcon::Active, QIcon::Off);
748     auto lockIconOffSelected = Utils::StyleHelper::IconFontHelper(
749         lockOffUnicode, Theme::getColor(Theme::DSnavigatorIconSelected), size, QIcon::Selected, QIcon::Off);
750     auto lockIconOnNormal = Utils::StyleHelper::IconFontHelper(
751         lockOnUnicode, Theme::getColor(Theme::DSnavigatorIcon), size, QIcon::Normal, QIcon::On);
752     auto lockIconOnHover = Utils::StyleHelper::IconFontHelper(
753         lockOnUnicode, Theme::getColor(Theme::DSnavigatorIconHover), size, QIcon::Active, QIcon::On);
754     auto lockIconOnSelected = Utils::StyleHelper::IconFontHelper(
755         lockOnUnicode, Theme::getColor(Theme::DSnavigatorIconSelected), size, QIcon::Selected, QIcon::On);
756 
757     const QIcon lockIcon = Utils::StyleHelper::getIconFromIconFont(
758                 fontName, {lockIconOffNormal,
759                            lockIconOffHover,
760                            lockIconOffSelected,
761                            lockIconOnNormal,
762                            lockIconOnHover,
763                            lockIconOnSelected});
764 
765     auto idDelegate = new NameItemDelegate(this);
766 
767     auto visibilityDelegate = new IconCheckboxItemDelegate(this, visibilityIcon);
768     auto aliasDelegate = new IconCheckboxItemDelegate(this, aliasIcon);
769     auto lockDelegate = new IconCheckboxItemDelegate(this, lockIcon);
770 
771     treeWidget()->setItemDelegateForColumn(NavigatorTreeModel::ColumnType::Name, idDelegate);
772     treeWidget()->setItemDelegateForColumn(NavigatorTreeModel::ColumnType::Alias, aliasDelegate);
773     treeWidget()->setItemDelegateForColumn(NavigatorTreeModel::ColumnType::Visibility, visibilityDelegate);
774     treeWidget()->setItemDelegateForColumn(NavigatorTreeModel::ColumnType::Lock, lockDelegate);
775 
776 #endif //QMLDESIGNER_TEST
777 }
778 
779 } // namespace QmlDesigner
780