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