/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "formeditorview.h" #include "selectiontool.h" #include "movetool.h" #include "option3daction.h" #include "resizetool.h" #include "dragtool.h" #include "formeditorwidget.h" #include #include "formeditoritem.h" #include "formeditorscene.h" #include "abstractcustomtool.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace QmlDesigner { FormEditorView::FormEditorView(QObject *parent) : AbstractView(parent) { } FormEditorScene* FormEditorView::scene() const { return m_scene.data(); } FormEditorView::~FormEditorView() { m_currentTool = nullptr; qDeleteAll(m_customToolList); } void FormEditorView::modelAttached(Model *model) { Q_ASSERT(model); temporaryBlockView(); AbstractView::modelAttached(model); Q_ASSERT(m_scene->formLayerItem()); if (QmlItemNode::isValidQmlItemNode(rootModelNode())) setupFormEditorItemTree(rootModelNode()); m_formEditorWidget->updateActions(); setupOption3DAction(); if (!rewriterView()->errors().isEmpty()) formEditorWidget()->showErrorMessageBox(rewriterView()->errors()); else formEditorWidget()->hideErrorMessageBox(); if (!rewriterView()->warnings().isEmpty()) formEditorWidget()->showWarningMessageBox(rewriterView()->warnings()); } //This function does the setup of the initial FormEditorItem tree in the scene void FormEditorView::setupFormEditorItemTree(const QmlItemNode &qmlItemNode) { m_scene->addFormEditorItem(qmlItemNode); foreach (const QmlObjectNode &nextNode, qmlItemNode.allDirectSubNodes()) //TODO instance children //If the node has source for components/custom parsers we ignore it. if (QmlItemNode::isValidQmlItemNode(nextNode) && nextNode.modelNode().nodeSourceType() == ModelNode::NodeWithoutSource) setupFormEditorItemTree(nextNode.toQmlItemNode()); } static void deleteWithoutChildren(const QList &items) { foreach (FormEditorItem *item, items) { foreach (QGraphicsItem *child, item->childItems()) { child->setParentItem(item->scene()->rootFormEditorItem()); } delete item; } } void FormEditorView::removeNodeFromScene(const QmlItemNode &qmlItemNode) { if (qmlItemNode.isValid()) { QList nodeList; nodeList.append(qmlItemNode.allSubModelNodes()); nodeList.append(qmlItemNode); QList removedItemList; removedItemList.append(scene()->itemsForQmlItemNodes(nodeList)); m_currentTool->itemsAboutToRemoved(removedItemList); //The destructor of QGraphicsItem does delete all its children. //We have to keep the children if they are not children in the model anymore. //Otherwise we delete the children explicitly anyway. deleteWithoutChildren(removedItemList); } } void FormEditorView::hideNodeFromScene(const QmlItemNode &qmlItemNode) { if (FormEditorItem *item = m_scene->itemForQmlItemNode(qmlItemNode)) { QList removedItems = scene()->itemsForQmlItemNodes(qmlItemNode.allSubModelNodes()); removedItems.append(item); m_currentTool->itemsAboutToRemoved(removedItems); item->setFormEditorVisible(false); } } void FormEditorView::createFormEditorWidget() { m_formEditorWidget = QPointer(new FormEditorWidget(this)); m_scene = QPointer(new FormEditorScene(m_formEditorWidget.data(), this)); m_moveTool = std::make_unique(this); m_selectionTool = std::make_unique(this); m_resizeTool = std::make_unique(this); m_dragTool = std::make_unique(this); m_currentTool = m_selectionTool.get(); auto formEditorContext = new Internal::FormEditorContext(m_formEditorWidget.data()); Core::ICore::addContextObject(formEditorContext); connect(formEditorWidget()->zoomAction(), &ZoomAction::zoomLevelChanged, [this]() { m_currentTool->formEditorItemsChanged(scene()->allFormEditorItems()); }); connect(formEditorWidget()->showBoundingRectAction(), &QAction::toggled, scene(), &FormEditorScene::setShowBoundingRects); } void FormEditorView::temporaryBlockView() { formEditorWidget()->graphicsView()->setUpdatesEnabled(false); static auto timer = new QTimer(qApp); timer->setSingleShot(true); timer->start(1000); connect(timer, &QTimer::timeout, this, [this]() { formEditorWidget()->graphicsView()->setUpdatesEnabled(true); }); } void FormEditorView::setupOption3DAction() { QTC_ASSERT(m_formEditorWidget->option3DAction(), return); auto import = Import::createLibraryImport("QtQuick3D", "1.0"); auto action = m_formEditorWidget->option3DAction(); if (model() && model()->hasImport(import, true, true)) { bool enabled = true; if (rootModelNode().hasAuxiliaryData("3d-view")) enabled = rootModelNode().auxiliaryData("3d-view").toBool(); action->set3DEnabled(enabled); action->setEnabled(true); } else { action->set3DEnabled(false); action->setEnabled(false); } } void FormEditorView::nodeCreated(const ModelNode &node) { //If the node has source for components/custom parsers we ignore it. if (QmlItemNode::isValidQmlItemNode(node) && node.nodeSourceType() == ModelNode::NodeWithoutSource) //only setup QmlItems setupFormEditorItemTree(QmlItemNode(node)); } void FormEditorView::modelAboutToBeDetached(Model *model) { m_currentTool->setItems(QList()); m_selectionTool->clear(); m_moveTool->clear(); m_resizeTool->clear(); m_dragTool->clear(); foreach (AbstractCustomTool *customTool, m_customToolList) customTool->clear(); m_scene->clearFormEditorItems(); m_formEditorWidget->updateActions(); m_formEditorWidget->resetView(); scene()->resetScene(); m_currentTool = m_selectionTool.get(); AbstractView::modelAboutToBeDetached(model); } void FormEditorView::importsChanged(const QList &/*addedImports*/, const QList &/*removedImports*/) { reset(); } void FormEditorView::nodeAboutToBeRemoved(const ModelNode &removedNode) { const QmlItemNode qmlItemNode(removedNode); removeNodeFromScene(qmlItemNode); } void FormEditorView::rootNodeTypeChanged(const QString &/*type*/, int /*majorVersion*/, int /*minorVersion*/) { foreach (FormEditorItem *item, m_scene->allFormEditorItems()) { item->setParentItem(nullptr); } foreach (FormEditorItem *item, m_scene->allFormEditorItems()) { m_scene->removeItemFromHash(item); delete item; } QmlItemNode newItemNode(rootModelNode()); if (newItemNode.isValid()) //only setup QmlItems setupFormEditorItemTree(newItemNode); m_currentTool->setItems(scene()->itemsForQmlItemNodes(toQmlItemNodeList(selectedModelNodes()))); } void FormEditorView::propertiesAboutToBeRemoved(const QList& propertyList) { QList removedItems; foreach (const AbstractProperty &property, propertyList) { if (property.isNodeAbstractProperty()) { NodeAbstractProperty nodeAbstractProperty = property.toNodeAbstractProperty(); foreach (const ModelNode &modelNode, nodeAbstractProperty.allSubNodes()) { const QmlItemNode qmlItemNode(modelNode); if (qmlItemNode.isValid()){ if (FormEditorItem *item = m_scene->itemForQmlItemNode(qmlItemNode)) { removedItems.append(item); delete item; } } } } } m_currentTool->itemsAboutToRemoved(removedItems); } static inline bool hasNodeSourceParent(const ModelNode &node) { if (node.hasParentProperty() && node.parentProperty().parentModelNode().isValid()) { ModelNode parent = node.parentProperty().parentModelNode(); if (parent.nodeSourceType() != ModelNode::NodeWithoutSource) return true; return hasNodeSourceParent(parent); } return false; } void FormEditorView::nodeReparented(const ModelNode &node, const NodeAbstractProperty &/*newPropertyParent*/, const NodeAbstractProperty &/*oldPropertyParent*/, AbstractView::PropertyChangeFlags /*propertyChange*/) { if (hasNodeSourceParent(node)) hideNodeFromScene(node); } WidgetInfo FormEditorView::widgetInfo() { if (!m_formEditorWidget) createFormEditorWidget(); return createWidgetInfo(m_formEditorWidget.data(), nullptr, "FormEditor", WidgetInfo::CentralPane, 0, tr("Form Editor"), DesignerWidgetFlags::IgnoreErrors); } FormEditorWidget *FormEditorView::formEditorWidget() { return m_formEditorWidget.data(); } void FormEditorView::nodeIdChanged(const ModelNode& node, const QString &/*newId*/, const QString &/*oldId*/) { QmlItemNode itemNode(node); if (itemNode.isValid() && node.nodeSourceType() == ModelNode::NodeWithoutSource) { FormEditorItem *item = m_scene->itemForQmlItemNode(itemNode); if (item) { if (node.isSelected()) { m_currentTool->setItems(scene()->itemsForQmlItemNodes(toQmlItemNodeList(selectedModelNodes()))); m_scene->update(); } item->update(); } } } void FormEditorView::selectedNodesChanged(const QList &selectedNodeList, const QList &/*lastSelectedNodeList*/) { m_currentTool->setItems(scene()->itemsForQmlItemNodes(toQmlItemNodeList(selectedNodeList))); m_scene->update(); } void FormEditorView::documentMessagesChanged(const QList &errors, const QList &) { if (!errors.isEmpty()) formEditorWidget()->showErrorMessageBox(errors); else formEditorWidget()->hideErrorMessageBox(); } void FormEditorView::customNotification(const AbstractView * /*view*/, const QString &identifier, const QList &/*nodeList*/, const QList &/*data*/) { if (identifier == QStringLiteral("puppet crashed")) m_dragTool->clearMoveDelay(); if (identifier == QStringLiteral("reset QmlPuppet")) temporaryBlockView(); } AbstractFormEditorTool* FormEditorView::currentTool() const { return m_currentTool; } bool FormEditorView::changeToMoveTool() { if (m_currentTool == m_moveTool.get()) return true; if (!isMoveToolAvailable()) return false; changeCurrentToolTo(m_moveTool.get()); return true; } void FormEditorView::changeToDragTool() { if (m_currentTool == m_dragTool.get()) return; changeCurrentToolTo(m_dragTool.get()); } bool FormEditorView::changeToMoveTool(const QPointF &beginPoint) { if (m_currentTool == m_moveTool.get()) return true; if (!isMoveToolAvailable()) return false; changeCurrentToolTo(m_moveTool.get()); m_moveTool->beginWithPoint(beginPoint); return true; } void FormEditorView::changeToSelectionTool() { if (m_currentTool == m_selectionTool.get()) return; changeCurrentToolTo(m_selectionTool.get()); } void FormEditorView::changeToSelectionTool(QGraphicsSceneMouseEvent *event) { if (m_currentTool == m_selectionTool.get()) return; changeCurrentToolTo(m_selectionTool.get()); m_selectionTool->selectUnderPoint(event); } void FormEditorView::changeToResizeTool() { if (m_currentTool == m_resizeTool.get()) return; changeCurrentToolTo(m_resizeTool.get()); } void FormEditorView::changeToTransformTools() { if (m_currentTool == m_moveTool.get() || m_currentTool == m_resizeTool.get() || m_currentTool == m_selectionTool.get()) return; changeToSelectionTool(); } void FormEditorView::changeToCustomTool() { if (hasSelectedModelNodes()) { int handlingRank = 0; AbstractCustomTool *selectedCustomTool = nullptr; const ModelNode selectedModelNode = selectedModelNodes().constFirst(); foreach (AbstractCustomTool *customTool, m_customToolList) { if (customTool->wantHandleItem(selectedModelNode) > handlingRank) { handlingRank = customTool->wantHandleItem(selectedModelNode); selectedCustomTool = customTool; } } if (handlingRank > 0 && selectedCustomTool) changeCurrentToolTo(selectedCustomTool); } } void FormEditorView::changeCurrentToolTo(AbstractFormEditorTool *newTool) { m_scene->updateAllFormEditorItems(); m_currentTool->clear(); m_currentTool = newTool; m_currentTool->clear(); m_currentTool->setItems(scene()->itemsForQmlItemNodes(toQmlItemNodeList( selectedModelNodes()))); m_currentTool->start(); } void FormEditorView::registerTool(AbstractCustomTool *tool) { tool->setView(this); m_customToolList.append(tool); } void FormEditorView::auxiliaryDataChanged(const ModelNode &node, const PropertyName &name, const QVariant &data) { AbstractView::auxiliaryDataChanged(node, name, data); if (name == "invisible") { if (FormEditorItem *item = scene()->itemForQmlItemNode(QmlItemNode(node))) { bool isInvisible = data.toBool(); if (item->isFormEditorVisible()) item->setVisible(!isInvisible); ModelNode newNode(node); if (isInvisible) newNode.deselectNode(); } } } void FormEditorView::instancesCompleted(const QVector &completedNodeList) { QList itemNodeList; foreach (const ModelNode &node, completedNodeList) { const QmlItemNode qmlItemNode(node); if (qmlItemNode.isValid()) { if (FormEditorItem *item = scene()->itemForQmlItemNode(qmlItemNode)) { scene()->synchronizeParent(qmlItemNode); itemNodeList.append(item); } } if (node.isRootNode()) formEditorWidget()->invalidate3DEditor(); } currentTool()->instancesCompleted(itemNodeList); } void FormEditorView::instanceInformationsChanged(const QMultiHash &informationChangedHash) { QList changedItems; const int rootElementInitWidth = DesignerSettings::getValue(DesignerSettingsKey::ROOT_ELEMENT_INIT_WIDTH).toInt(); const int rootElementInitHeight = DesignerSettings::getValue(DesignerSettingsKey::ROOT_ELEMENT_INIT_HEIGHT).toInt(); QList informationChangedNodes = Utils::filtered(informationChangedHash.keys(), [](const ModelNode &node) { return QmlItemNode::isValidQmlItemNode(node); }); foreach (const ModelNode &node, informationChangedNodes) { const QmlItemNode qmlItemNode(node); if (FormEditorItem *item = scene()->itemForQmlItemNode(qmlItemNode)) { scene()->synchronizeTransformation(item); if (qmlItemNode.isRootModelNode() && informationChangedHash.values(node).contains(Size)) { if (qmlItemNode.instanceBoundingRect().isEmpty() && !(qmlItemNode.propertyAffectedByCurrentState("width") && qmlItemNode.propertyAffectedByCurrentState("height"))) { if (!(rootModelNode().hasAuxiliaryData("width"))) rootModelNode().setAuxiliaryData("width", rootElementInitWidth); if (!(rootModelNode().hasAuxiliaryData("height"))) rootModelNode().setAuxiliaryData("height", rootElementInitHeight); rootModelNode().setAuxiliaryData("autoSize", true); formEditorWidget()->updateActions(); } else { if (rootModelNode().hasAuxiliaryData("autoSize") && (qmlItemNode.propertyAffectedByCurrentState("width") || qmlItemNode.propertyAffectedByCurrentState("height"))) { rootModelNode().setAuxiliaryData("width", QVariant()); rootModelNode().setAuxiliaryData("height", QVariant()); rootModelNode().removeAuxiliaryData("autoSize"); formEditorWidget()->updateActions(); } } formEditorWidget()->setRootItemRect(qmlItemNode.instanceBoundingRect()); formEditorWidget()->centerScene(); } changedItems.append(item); } } m_currentTool->formEditorItemsChanged(changedItems); } void FormEditorView::instancesRenderImageChanged(const QVector &nodeList) { foreach (const ModelNode &node, nodeList) { if (QmlItemNode::isValidQmlItemNode(node)) if (FormEditorItem *item = scene()->itemForQmlItemNode(QmlItemNode(node))) item->update(); } } void FormEditorView::instancesChildrenChanged(const QVector &nodeList) { QList changedItems; foreach (const ModelNode &node, nodeList) { const QmlItemNode qmlItemNode(node); if (qmlItemNode.isValid()) { if (FormEditorItem *item = scene()->itemForQmlItemNode(qmlItemNode)) { scene()->synchronizeParent(qmlItemNode); changedItems.append(item); } } } m_currentTool->formEditorItemsChanged(changedItems); m_currentTool->instancesParentChanged(changedItems); } void FormEditorView::rewriterBeginTransaction() { m_transactionCounter++; } void FormEditorView::rewriterEndTransaction() { m_transactionCounter--; } double FormEditorView::containerPadding() const { return m_formEditorWidget->containerPadding(); } double FormEditorView::spacing() const { return m_formEditorWidget->spacing(); } void FormEditorView::gotoError(int line, int column) { if (m_gotoErrorCallback) m_gotoErrorCallback(line, column); } void FormEditorView::setGotoErrorCallback(std::function gotoErrorCallback) { m_gotoErrorCallback = gotoErrorCallback; } void FormEditorView::exportAsImage() { m_formEditorWidget->exportAsImage(m_scene->rootFormEditorItem()->boundingRect()); } void FormEditorView::toggle3DViewEnabled(bool enabled) { QTC_ASSERT(model(), return); QTC_ASSERT(rootModelNode().isValid(), return); if (enabled) rootModelNode().removeAuxiliaryData("3d-view"); else rootModelNode().setAuxiliaryData("3d-view", false); formEditorWidget()->set3dEditorVisibility(enabled); } QmlItemNode findRecursiveQmlItemNode(const QmlObjectNode &firstQmlObjectNode) { QmlObjectNode qmlObjectNode = firstQmlObjectNode; while (true) { QmlItemNode itemNode = qmlObjectNode.toQmlItemNode(); if (itemNode.isValid()) return itemNode; if (qmlObjectNode.hasInstanceParent()) qmlObjectNode = qmlObjectNode.instanceParent(); else break; } return QmlItemNode(); } void FormEditorView::instancePropertyChanged(const QList > &propertyList) { QList changedItems; foreach (auto &nodePropertyPair, propertyList) { const QmlItemNode qmlItemNode(nodePropertyPair.first); const PropertyName propertyName = nodePropertyPair.second; if (qmlItemNode.isValid()) { if (FormEditorItem *item = scene()->itemForQmlItemNode(qmlItemNode)) { static const PropertyNameList skipList({"x", "y", "width", "height"}); if (!skipList.contains(propertyName)) { m_scene->synchronizeOtherProperty(item, propertyName); changedItems.append(item); } } } } m_currentTool->formEditorItemsChanged(changedItems); } bool FormEditorView::isMoveToolAvailable() const { if (hasSingleSelectedModelNode() && QmlItemNode::isValidQmlItemNode(singleSelectedModelNode())) { QmlItemNode selectedQmlItemNode(singleSelectedModelNode()); return selectedQmlItemNode.instanceIsMovable() && selectedQmlItemNode.modelIsMovable() && !selectedQmlItemNode.instanceIsInLayoutable(); } return true; } void FormEditorView::reset() { QTimer::singleShot(200, this, &FormEditorView::delayedReset); } void FormEditorView::delayedReset() { m_selectionTool->clear(); m_moveTool->clear(); m_resizeTool->clear(); m_dragTool->clear(); m_scene->clearFormEditorItems(); if (isAttached() && QmlItemNode::isValidQmlItemNode(rootModelNode())) setupFormEditorItemTree(rootModelNode()); setupOption3DAction(); } }