diff --git a/src/plugins/qmldesigner/designercore/designercore.pri b/src/plugins/qmldesigner/designercore/designercore.pri index 744d62bcbcb..539f241c9aa 100644 --- a/src/plugins/qmldesigner/designercore/designercore.pri +++ b/src/plugins/qmldesigner/designercore/designercore.pri @@ -75,7 +75,8 @@ SOURCES += $$PWD/model/abstractview.cpp \ $$PWD/model/rewriteactioncompressor.cpp \ $$PWD/model/qmltextgenerator.cpp \ $$PWD/model/modelmerger.cpp \ - $$PWD/exceptions/rewritingexception.cpp + $$PWD/exceptions/rewritingexception.cpp \ + $$PWD/model/modelnodecontextmenu.cpp HEADERS += $$PWD/include/corelib_global.h \ $$PWD/include/abstractview.h \ @@ -145,7 +146,8 @@ HEADERS += $$PWD/include/corelib_global.h \ $$PWD/include/modelmerger.h \ $$PWD/include/mathutils.h \ $$PWD/include/customnotifications.h \ - $$PWD/include/rewritingexception.h + $$PWD/include/rewritingexception.h \ + $$PWD//model/modelnodecontextmenu.h contains(CONFIG, plugin) { # If core.pri has been included in the qmldesigner plugin diff --git a/src/plugins/qmldesigner/designercore/include/qmlmodelview.h b/src/plugins/qmldesigner/designercore/include/qmlmodelview.h index ff5ee02c312..67336ed684c 100644 --- a/src/plugins/qmldesigner/designercore/include/qmlmodelview.h +++ b/src/plugins/qmldesigner/designercore/include/qmlmodelview.h @@ -34,6 +34,7 @@ #define QMLMODELVIEW_H #include +#include #include #include "qmlitemnode.h" #include "qmlstate.h" @@ -121,6 +122,8 @@ public: void importsChanged(const QList &addedImports, const QList &removedImports); void nodeSourceChanged(const ModelNode &modelNode, const QString &newNodeSource); + void showContextMenu(const QPoint &globalPos, const QPoint &scenePos, bool showSelection); + protected: NodeInstance instanceForModelNode(const ModelNode &modelNode); bool hasInstanceForModelNode(const ModelNode &modelNode); diff --git a/src/plugins/qmldesigner/designercore/model/modelnodecontextmenu.cpp b/src/plugins/qmldesigner/designercore/model/modelnodecontextmenu.cpp new file mode 100644 index 00000000000..6704b80ef6f --- /dev/null +++ b/src/plugins/qmldesigner/designercore/model/modelnodecontextmenu.cpp @@ -0,0 +1,424 @@ +#include "modelnodecontextmenu.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace QmlDesigner { + +static inline QString captionForModelNode(const ModelNode &modelNode) +{ + if (modelNode.id().isEmpty()) + return modelNode.simplifiedTypeName(); + + return modelNode.id(); +} + +static inline bool contains(const QmlItemNode &node, const QPoint &p) +{ + return node.instanceSceneTransform().mapRect(node.instanceBoundingRect()).contains(p); +} + +static inline bool checkIfNodeIsAView(const ModelNode &node) +{ + return node.metaInfo().isValid() && + (node.metaInfo().isSubclassOf("QtQuick.ListView", -1, -1) || + node.metaInfo().isSubclassOf("QtQuick.GridView", -1, -1) || + node.metaInfo().isSubclassOf("QtQuick.PathView", -1, -1)); +} + +static inline QList siblingsForNode(const QmlItemNode &itemNode) +{ + QList siblingList; + + if (itemNode.isValid() && itemNode.modelNode().parentProperty().isValid()) { + QList modelNodes = itemNode.modelNode().parentProperty().parentModelNode().allDirectSubModelNodes(); + foreach (const ModelNode &node, modelNodes) { + QmlItemNode childItemNode = node; + if (childItemNode.isValid()) + siblingList.append(childItemNode); + } + } + + return siblingList; +} + +static signed int getMaxZValue(const QList &siblingList) +{ + signed int maximum = INT_MIN; + foreach (const QmlItemNode &node, siblingList) { + signed int z = node.instanceValue("z").toInt(); + if (z > maximum) + maximum = z; + } + return maximum; +} + +static signed int getMinZValue(const QList &siblingList) +{ + signed int minimum = INT_MAX; + foreach (const QmlItemNode &node, siblingList) { + signed int z = node.instanceValue("z").toInt(); + if (z < minimum) + minimum = z; + } + return minimum; +} + + +static inline bool modelNodeIsComponent(const ModelNode &node) +{ + if (!node.isValid() || !node.metaInfo().isValid()) + return false; + + if (node.metaInfo().isComponent()) + return true; + + if (node.nodeSourceType() == ModelNode::ComponentSource) + return true; + if (checkIfNodeIsAView(node) && + node.hasNodeProperty("delegate")) { + if (node.nodeProperty("delegate").modelNode().metaInfo().isComponent()) + return true; + if (node.nodeProperty("delegate").modelNode().nodeSourceType() == ModelNode::ComponentSource) + return true; + } + + return false; +} + +static inline bool isFileComponent(const ModelNode &node) +{ + if (!node.isValid() || !node.metaInfo().isValid()) + return false; + + if (node.metaInfo().isComponent()) + return true; + + if (checkIfNodeIsAView(node) && + node.hasNodeProperty("delegate")) { + if (node.nodeProperty("delegate").modelNode().metaInfo().isComponent()) + return true; + } + + return false; +} + +static inline void openFileForComponent(const ModelNode &node) +{ + if (node.metaInfo().isComponent()) { + Core::EditorManager::instance()->openEditor(node.metaInfo().componentFileName()); + } else if (checkIfNodeIsAView(node) && + node.hasNodeProperty("delegate") && + node.nodeProperty("delegate").modelNode().metaInfo().isComponent()) { + Core::EditorManager::instance()->openEditor(node.nodeProperty("delegate").modelNode().metaInfo().componentFileName()); + } +} + +static inline void openInlineComponent(const ModelNode &node) +{ + if (!node.isValid() || !node.metaInfo().isValid()) + return; + + if (!DesignDocumentController::instance()) + return; + + if (node.nodeSourceType() == ModelNode::ComponentSource) + DesignDocumentController::instance()->changeCurrentModelTo(node); + if (checkIfNodeIsAView(node) && + node.hasNodeProperty("delegate")) { + if (node.nodeProperty("delegate").modelNode().nodeSourceType() == ModelNode::ComponentSource) + DesignDocumentController::instance()->changeCurrentModelTo(node.nodeProperty("delegate").modelNode()); + } +} + +ModelNodeContextMenu::ModelNodeContextMenu(QmlModelView *view) : m_view(view) +{ +} + +void ModelNodeContextMenu::execute(const QPoint &pos, bool selectionMenuBool) +{ + QMenu* menu = new QMenu(); + + bool singleSelected = false; + bool selectionIsEmpty = m_view->selectedModelNodes().isEmpty(); + ModelNode currentSingleNode; + if (m_view->selectedModelNodes().count()== 1) { + singleSelected = true; + currentSingleNode = m_view->selectedModelNodes().first(); + } + + if (selectionMenuBool) { + QMenu *selectionMenu = new QMenu(QApplication::translate("ModelNodeContextMenu", "Selecion"), menu); + menu->addMenu(selectionMenu); + ModelNode parentNode; + if (singleSelected) { + //ModelNodeAction *selectionAction; + //selectionAction = createModelNodeAction(QApplication::translate("ModelNodeContextMenu", "DeSelect: ") + captionForModelNode(currentSingleNode), selectionMenu, QList() << currentSingleNode, ModelNodeAction::DeSelectModelNode); + //selectionMenu->addAction(selectionAction); + if (!currentSingleNode.isRootNode()) { + parentNode = currentSingleNode.parentProperty().parentModelNode(); + selectionMenu->addAction(createModelNodeAction(QApplication::translate("ModelNodeContextMenu", "Select parent: ") + captionForModelNode(parentNode), + selectionMenu, QList() << parentNode, ModelNodeAction::SelectModelNode)); + } + + selectionMenu->addSeparator(); + } + foreach (const ModelNode &node, m_view->allModelNodes()) { + if (node != currentSingleNode && node != parentNode && contains(node, m_scenePos) && !node.isRootNode()) + selectionMenu->addAction(createModelNodeAction(QApplication::translate("ModelNodeContextMenu", "Select: ") + captionForModelNode(node), selectionMenu, QList() << node, ModelNodeAction::SelectModelNode)); + } + } + + QMenu *stackMenu = new QMenu(QApplication::translate("ModelNodeContextMenu", "Stack (z)"), menu); + menu->addMenu(stackMenu); + + stackMenu->addAction(createModelNodeAction(QApplication::translate("ModelNodeContextMenu", "To Front"), stackMenu, QList() << currentSingleNode, ModelNodeAction::ToFront, singleSelected)); + stackMenu->addAction(createModelNodeAction(QApplication::translate("ModelNodeContextMenu", "To Back"), stackMenu, QList() << currentSingleNode, ModelNodeAction::ToBack, singleSelected)); + stackMenu->addAction(createModelNodeAction(QApplication::translate("ModelNodeContextMenu", "Raise"), stackMenu, QList() << m_view->selectedModelNodes(), ModelNodeAction::Raise)); + stackMenu->addAction(createModelNodeAction(QApplication::translate("ModelNodeContextMenu", "Lower"), stackMenu, QList() << m_view->selectedModelNodes(), ModelNodeAction::Lower)); + stackMenu->addSeparator(); + stackMenu->addAction(createModelNodeAction(QApplication::translate("ModelNodeContextMenu", "Reset z property"), stackMenu, QList() << m_view->selectedModelNodes(), ModelNodeAction::ResetZ)); + + QMenu *editMenu = new QMenu(QApplication::translate("ModelNodeContextMenu", "Edit"), menu); + menu->addMenu(editMenu); + if (!selectionIsEmpty) { + //editMenu->addAction(createModelNodeAction(QApplication::translate("ModelNodeContextMenu", "Change Id"), editMenu, QList() << currentSingleNode, ModelNodeAction::SetId, singleSelected)); + editMenu->addAction(createModelNodeAction(QApplication::translate("ModelNodeContextMenu", "Reset Position"), editMenu, m_view->selectedModelNodes(), ModelNodeAction::ResetPosition)); + editMenu->addAction(createModelNodeAction(QApplication::translate("ModelNodeContextMenu", "Reset Size"), editMenu, m_view->selectedModelNodes(), ModelNodeAction::ResetSize)); + editMenu->addAction(createModelNodeAction(QApplication::translate("ModelNodeContextMenu", "Visibility"), editMenu, QList() << currentSingleNode, ModelNodeAction::ModelNodeVisibility, singleSelected)); + + } else { + editMenu->setEnabled(false); + } + + menu->addSeparator(); + bool enterComponent = false; + if (singleSelected) { + enterComponent = modelNodeIsComponent(currentSingleNode); + } + menu->addAction(createModelNodeAction(QApplication::translate("ModelNodeContextMenu", "Go into Component"), editMenu, QList() << currentSingleNode, ModelNodeAction::EnterComponent, enterComponent)); + + menu->exec(pos); + menu->deleteLater(); +} + +void ModelNodeContextMenu::setScenePos(const QPoint &pos) +{ + m_scenePos = pos; +} + + +ModelNodeAction* ModelNodeContextMenu::createModelNodeAction(const QString &description, QMenu *menu, const QList &modelNodeList, ModelNodeAction::ModelNodeActionType type, bool enabled) +{ + ModelNodeAction* action = new ModelNodeAction(description, menu, m_view, modelNodeList, type); + action->setEnabled(enabled); + return action; +} + + +ModelNodeAction::ModelNodeAction( const QString & text, QObject *parent, QmlModelView *view, const QList &modelNodeList, ModelNodeActionType type) : + QAction(text, parent), m_view(view), m_modelNodeList(modelNodeList), m_type(type) +{ + if (type == ModelNodeVisibility) { + setCheckable(true); + QmlItemNode itemNode = QmlItemNode(m_modelNodeList.first()); + if (itemNode.isValid()) + setChecked(itemNode.instanceValue("visible").toBool()); + else + setEnabled(false); + } + connect(this, SIGNAL(triggered(bool)), this, SLOT(actionTriggered(bool))); +} + +void ModelNodeAction::actionTriggered(bool b) +{ + switch (m_type) { + case ModelNodeAction::SelectModelNode: select(); break; + case ModelNodeAction::DeSelectModelNode: deSelect(); break; + case ModelNodeAction::CutSelection: cut(); break; + case ModelNodeAction::CopySelection: copy(); break; + case ModelNodeAction::DeleteSelection: deleteSelection(); break; + case ModelNodeAction::ToFront: toFront(); break; + case ModelNodeAction::ToBack: toBack(); break; + case ModelNodeAction::Raise: raise(); break; + case ModelNodeAction::Lower: lower(); break; + case ModelNodeAction::Paste: paste(); break; + case ModelNodeAction::Undo: undo(); break; + case ModelNodeAction::Redo: redo(); break; + case ModelNodeAction::ModelNodeVisibility: setVisible(b); break; + case ModelNodeAction::ResetSize: resetSize(); break; + case ModelNodeAction::ResetPosition: resetPosition(); break; + case ModelNodeAction::EnterComponent: enterComponent(); break; + case ModelNodeAction::SetId: setId(); break; + case ModelNodeAction::ResetZ: resetZ(); break; + } +} + +void ModelNodeAction::select() +{ + if (m_view) + m_view->setSelectedModelNodes(m_modelNodeList); +} + +void ModelNodeAction::deSelect() +{ + if (m_view) { + QList selectedNodes = m_view->selectedModelNodes(); + foreach (const ModelNode &node, m_modelNodeList) { + if (selectedNodes.contains(node)) + selectedNodes.removeAll(node); + } + m_view->setSelectedModelNodes(selectedNodes); + } +} + +void ModelNodeAction::cut() +{ +} + + +void ModelNodeAction::copy() +{ +} + +void ModelNodeAction::deleteSelection() +{ +} + +void ModelNodeAction::toFront() +{ + if (!m_view) + return; + + QmlItemNode node = m_modelNodeList.first(); + if (node.isValid()) { + signed int maximumZ = getMaxZValue(siblingsForNode(node)); + maximumZ++; + node.setVariantProperty("z", maximumZ); + } +} + + +void ModelNodeAction::toBack() +{ + if (!m_view) + return; + + QmlItemNode node = m_modelNodeList.first(); + if (node.isValid()) { + signed int minimumZ = getMinZValue(siblingsForNode(node)); + minimumZ--; + node.setVariantProperty("z", minimumZ); + } +} + +void ModelNodeAction::raise() +{ + if (!m_view) + return; + + RewriterTransaction(m_view); + foreach (ModelNode modelNode, m_modelNodeList) { + QmlItemNode node = modelNode; + if (node.isValid()) { + signed int z = node.instanceValue("z").toInt(); + z++; + node.setVariantProperty("z", z); + } + } +} + +void ModelNodeAction::lower() +{ + if (!m_view) + return; + + RewriterTransaction(m_view); + foreach (ModelNode modelNode, m_modelNodeList) { + QmlItemNode node = modelNode; + if (node.isValid()) { + signed int z = node.instanceValue("z").toInt(); + z--; + node.setVariantProperty("z", z); + } + } +} + +void ModelNodeAction::paste() +{ +} + +void ModelNodeAction::undo() +{ +} + +void ModelNodeAction::redo() +{ +} + +void ModelNodeAction::setVisible(bool b) +{ + m_modelNodeList.first().variantProperty("visible") = b; +} + + +void ModelNodeAction::resetSize() +{ + if (!m_view) + return; + + RewriterTransaction(m_view); + foreach (ModelNode node, m_modelNodeList) { + node.removeProperty("width"); + node.removeProperty("height"); + } +} + +void ModelNodeAction::resetPosition() +{ + if (!m_view) + return; + + RewriterTransaction(m_view); + foreach (ModelNode node, m_modelNodeList) { + node.removeProperty("x"); + node.removeProperty("y"); + } +} + +void ModelNodeAction::enterComponent() +{ + const ModelNode node = m_modelNodeList.first(); + if (node.isValid()) { + if (isFileComponent(node)) + openFileForComponent(node); + else + openInlineComponent(node); + } +} + +void ModelNodeAction::setId() +{ +} + +void ModelNodeAction::resetZ() +{ + if (!m_view) + return; + + RewriterTransaction(m_view); + foreach (ModelNode node, m_modelNodeList) { + node.removeProperty("z"); + } +} + +} diff --git a/src/plugins/qmldesigner/designercore/model/modelnodecontextmenu.h b/src/plugins/qmldesigner/designercore/model/modelnodecontextmenu.h new file mode 100644 index 00000000000..f333745f873 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/model/modelnodecontextmenu.h @@ -0,0 +1,90 @@ +#ifndef MODELNODECONTEXTMENU_H +#define MODELNODECONTEXTMENU_H + +#include +#include +#include +#include +#include + +namespace QmlDesigner { + +class ModelNodeAction : public QAction +{ + Q_OBJECT +public: + enum ModelNodeActionType { + SelectModelNode, + DeSelectModelNode, + CutSelection, + CopySelection, + DeleteSelection, + ToFront, + ToBack, + Raise, + Lower, + Paste, + Undo, + Redo, + ModelNodeVisibility, + ResetSize, + ResetPosition, + EnterComponent, + SetId, + ResetZ + }; + + + ModelNodeAction( const QString & text, QObject *parent, QmlModelView *view, const QList &modelNodeList, ModelNodeActionType type); + +public slots: + void actionTriggered(bool); + +private: + void select(); + void deSelect(); + void cut(); + void copy(); + void deleteSelection(); + void toFront(); + void toBack(); + void raise(); + void lower(); + void paste(); + void undo(); + void redo(); + void setVisible(bool); + void resetSize(); + void resetPosition(); + void enterComponent(); + void setId(); + void resetZ(); + + QmlModelView *m_view; + QList m_modelNodeList; + ModelNodeActionType m_type; +}; + +class ModelNodeContextMenu +{ +public: + ModelNodeContextMenu(QmlModelView *view); + void execute(const QPoint &pos, bool selecetionMenu); + void setScenePos(const QPoint &pos); + +signals: + +public slots: + +private: + ModelNodeAction* createModelNodeAction(const QString &description, QMenu *menu, const QList &modelNodeList, ModelNodeAction::ModelNodeActionType type, bool enabled = true); + + QmlModelView *m_view; + QPoint m_scenePos; + +}; + + +}; + +#endif // MODELNODECONTEXTMENU_H diff --git a/src/plugins/qmldesigner/designercore/model/qmlmodelview.cpp b/src/plugins/qmldesigner/designercore/model/qmlmodelview.cpp index 9eb593cb2fa..4252b8f98ba 100644 --- a/src/plugins/qmldesigner/designercore/model/qmlmodelview.cpp +++ b/src/plugins/qmldesigner/designercore/model/qmlmodelview.cpp @@ -48,6 +48,7 @@ #include "rewriterview.h" #include "plaintexteditmodifier.h" #include "modelmerger.h" +#include "modelnodecontextmenu.h" namespace QmlDesigner { @@ -418,6 +419,13 @@ void QmlModelView::nodeSourceChanged(const ModelNode &, const QString & /*newNod } +void QmlModelView::showContextMenu(const QPoint &globalPos, const QPoint &scenePos, bool showSelection) +{ + ModelNodeContextMenu contextMenu(this); + contextMenu.setScenePos(scenePos); + contextMenu.execute(globalPos, showSelection); +} + void QmlModelView::rewriterBeginTransaction() {