From 3d78fef4ef562c0120c905b824abb6ba8bd68965 Mon Sep 17 00:00:00 2001 From: Aleksei German Date: Thu, 22 Oct 2020 17:13:58 +0200 Subject: [PATCH] QmlDesigner: Rotation Tool - Added Rotation Tool to Form Editor - Rotation Tool is activated on items corners - Supports alt and shift modifiers - Works with MCU projects - Uses new rotation icon Task: QDS-2881 Change-Id: I9905968b7ac31d008184e343294fbd33cdb45c1c Reviewed-by: Marco Bubke Reviewed-by: Thomas Hartmann --- src/plugins/qmldesigner/CMakeLists.txt | 5 + .../components/formeditor/formeditor.pri | 10 + .../components/formeditor/formeditorview.cpp | 15 +- .../components/formeditor/formeditorview.h | 3 + .../components/formeditor/movetool.cpp | 15 + .../components/formeditor/movetool.h | 2 + .../components/formeditor/resizeindicator.cpp | 2 +- .../components/formeditor/resizetool.cpp | 16 +- .../components/formeditor/resizetool.h | 2 + .../formeditor/rotationcontroller.cpp | 244 ++++++++++++++++ .../formeditor/rotationcontroller.h | 92 +++++++ .../formeditor/rotationhandleitem.cpp | 119 ++++++++ .../formeditor/rotationhandleitem.h | 72 +++++ .../formeditor/rotationindicator.cpp | 98 +++++++ .../components/formeditor/rotationindicator.h | 62 +++++ .../formeditor/rotationmanipulator.cpp | 260 ++++++++++++++++++ .../formeditor/rotationmanipulator.h | 82 ++++++ .../components/formeditor/rotationtool.cpp | 203 ++++++++++++++ .../components/formeditor/rotationtool.h | 75 +++++ .../components/formeditor/selectiontool.cpp | 27 +- .../components/formeditor/selectiontool.h | 2 + .../designercore/include/qmlitemnode.h | 5 + .../designercore/model/qmlitemnode.cpp | 57 ++++ src/plugins/qmldesigner/qmldesignerplugin.qbs | 10 + 24 files changed, 1462 insertions(+), 16 deletions(-) create mode 100644 src/plugins/qmldesigner/components/formeditor/rotationcontroller.cpp create mode 100644 src/plugins/qmldesigner/components/formeditor/rotationcontroller.h create mode 100644 src/plugins/qmldesigner/components/formeditor/rotationhandleitem.cpp create mode 100644 src/plugins/qmldesigner/components/formeditor/rotationhandleitem.h create mode 100644 src/plugins/qmldesigner/components/formeditor/rotationindicator.cpp create mode 100644 src/plugins/qmldesigner/components/formeditor/rotationindicator.h create mode 100644 src/plugins/qmldesigner/components/formeditor/rotationmanipulator.cpp create mode 100644 src/plugins/qmldesigner/components/formeditor/rotationmanipulator.h create mode 100644 src/plugins/qmldesigner/components/formeditor/rotationtool.cpp create mode 100644 src/plugins/qmldesigner/components/formeditor/rotationtool.h diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index 48c1f2cc874..eaf5df1d5a5 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -265,6 +265,11 @@ extend_qtc_plugin(QmlDesigner resizeindicator.cpp resizeindicator.h resizemanipulator.cpp resizemanipulator.h resizetool.cpp resizetool.h + rotationtool.cpp rotationtool.h + rotationindicator.cpp rotationindicator.h + rotationcontroller.cpp rotationcontroller.h + rotationhandleitem.cpp rotationhandleitem.h + rotationmanipulator.cpp rotationmanipulator.h rubberbandselectionmanipulator.cpp rubberbandselectionmanipulator.h scaleitem.cpp scaleitem.h scalemanipulator.cpp scalemanipulator.h diff --git a/src/plugins/qmldesigner/components/formeditor/formeditor.pri b/src/plugins/qmldesigner/components/formeditor/formeditor.pri index 4609b277f90..833260dd1a0 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditor.pri +++ b/src/plugins/qmldesigner/components/formeditor/formeditor.pri @@ -24,6 +24,11 @@ SOURCES += formeditoritem.cpp \ scaleitem.cpp \ resizecontroller.cpp \ resizehandleitem.cpp \ + rotationtool.cpp \ + rotationindicator.cpp \ + rotationcontroller.cpp \ + rotationhandleitem.cpp \ + rotationmanipulator.cpp \ dragtool.cpp \ toolbox.cpp \ formeditorgraphicsview.cpp \ @@ -64,6 +69,11 @@ HEADERS += formeditorscene.h \ scaleitem.h \ resizecontroller.h \ resizehandleitem.h \ + rotationtool.h \ + rotationindicator.h \ + rotationcontroller.h \ + rotationhandleitem.h \ + rotationmanipulator.h \ dragtool.h \ toolbox.h \ formeditorgraphicsview.h \ diff --git a/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp b/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp index 6fb05cda0c7..ac67e58394f 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp +++ b/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp @@ -26,6 +26,7 @@ #include "formeditorview.h" #include "nodeinstanceview.h" #include "selectiontool.h" +#include "rotationtool.h" #include "movetool.h" #include "resizetool.h" #include "dragtool.h" @@ -207,6 +208,7 @@ void FormEditorView::createFormEditorWidget() m_moveTool = std::make_unique(this); m_selectionTool = std::make_unique(this); + m_rotationTool = std::make_unique(this); m_resizeTool = std::make_unique(this); m_dragTool = std::make_unique(this); @@ -248,6 +250,7 @@ void FormEditorView::cleanupToolsAndScene() { m_currentTool->setItems(QList()); m_selectionTool->clear(); + m_rotationTool->clear(); m_moveTool->clear(); m_resizeTool->clear(); m_dragTool->clear(); @@ -548,6 +551,12 @@ void FormEditorView::resetToSelectionTool() changeCurrentToolTo(m_selectionTool.get()); } +void FormEditorView::changeToRotationTool() { + if (m_currentTool == m_rotationTool.get()) + return; + changeCurrentToolTo(m_rotationTool.get()); +} + void FormEditorView::changeToResizeTool() { if (m_currentTool == m_resizeTool.get()) @@ -558,8 +567,9 @@ void FormEditorView::changeToResizeTool() void FormEditorView::changeToTransformTools() { if (m_currentTool == m_moveTool.get() || - m_currentTool == m_resizeTool.get() || - m_currentTool == m_selectionTool.get()) + m_currentTool == m_resizeTool.get() || + m_currentTool == m_rotationTool.get() || + m_currentTool == m_selectionTool.get()) return; changeToSelectionTool(); } @@ -854,6 +864,7 @@ void FormEditorView::reset() void FormEditorView::delayedReset() { m_selectionTool->clear(); + m_rotationTool->clear(); m_moveTool->clear(); m_resizeTool->clear(); m_dragTool->clear(); diff --git a/src/plugins/qmldesigner/components/formeditor/formeditorview.h b/src/plugins/qmldesigner/components/formeditor/formeditorview.h index 5072ccf5c55..ba2b79df994 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditorview.h +++ b/src/plugins/qmldesigner/components/formeditor/formeditorview.h @@ -47,6 +47,7 @@ class AbstractFormEditorTool; class AbstractCustomTool; class MoveTool; class SelectionTool; +class RotationTool; class ResizeTool; class DragTool; class ItemLibraryEntry; @@ -104,6 +105,7 @@ public: void changeToSelectionTool(); void changeToSelectionTool(QGraphicsSceneMouseEvent *event); void resetToSelectionTool(); + void changeToRotationTool(); void changeToResizeTool(); void changeToTransformTools(); void changeToCustomTool(); @@ -152,6 +154,7 @@ private: QList m_customToolList; std::unique_ptr m_moveTool; std::unique_ptr m_selectionTool; + std::unique_ptr m_rotationTool; std::unique_ptr m_resizeTool; std::unique_ptr m_dragTool; AbstractFormEditorTool *m_currentTool = nullptr; diff --git a/src/plugins/qmldesigner/components/formeditor/movetool.cpp b/src/plugins/qmldesigner/components/formeditor/movetool.cpp index ee2f1d57aa7..a383a746590 100644 --- a/src/plugins/qmldesigner/components/formeditor/movetool.cpp +++ b/src/plugins/qmldesigner/components/formeditor/movetool.cpp @@ -31,6 +31,7 @@ #include "formeditorgraphicsview.h" #include "resizehandleitem.h" +#include "rotationhandleitem.h" #include @@ -46,6 +47,7 @@ MoveTool::MoveTool(FormEditorView *editorView) m_moveManipulator(editorView->scene()->manipulatorLayerItem(), editorView), m_selectionIndicator(editorView->scene()->manipulatorLayerItem()), m_resizeIndicator(editorView->scene()->manipulatorLayerItem()), + m_rotationIndicator(editorView->scene()->manipulatorLayerItem()), m_anchorIndicator(editorView->scene()->manipulatorLayerItem()), m_bindingIndicator(editorView->scene()->manipulatorLayerItem()), m_contentNotEditableIndicator(editorView->scene()->manipulatorLayerItem()) @@ -61,6 +63,7 @@ void MoveTool::clear() m_movingItems.clear(); m_selectionIndicator.clear(); m_resizeIndicator.clear(); + m_rotationIndicator.clear(); m_anchorIndicator.clear(); m_bindingIndicator.clear(); m_contentNotEditableIndicator.clear(); @@ -100,6 +103,7 @@ void MoveTool::mouseMoveEvent(const QList &itemList, m_selectionIndicator.hide(); m_resizeIndicator.hide(); + m_rotationIndicator.hide(); m_anchorIndicator.hide(); m_bindingIndicator.hide(); @@ -135,6 +139,12 @@ void MoveTool::hoverMoveEvent(const QList &itemList, return; } + RotationHandleItem* rotationHandle = RotationHandleItem::fromGraphicsItem(itemList.constFirst()); + if (rotationHandle) { + view()->changeToRotationTool(); + return; + } + if (view()->hasSingleSelectedModelNode() && selectedItemCursorInMovableArea(event->scenePos())) return; @@ -182,6 +192,7 @@ void MoveTool::keyPressEvent(QKeyEvent *event) m_moveManipulator.setItems(movableItems); // m_selectionIndicator.hide(); m_resizeIndicator.hide(); + m_rotationIndicator.hide(); m_anchorIndicator.hide(); m_bindingIndicator.hide(); m_moveManipulator.beginRewriterTransaction(); @@ -215,6 +226,7 @@ void MoveTool::keyReleaseEvent(QKeyEvent *keyEvent) m_moveManipulator.clear(); // m_selectionIndicator.show(); m_resizeIndicator.show(); + m_rotationIndicator.show(); m_anchorIndicator.show(); m_bindingIndicator.show(); } @@ -241,6 +253,7 @@ void MoveTool::mouseReleaseEvent(const QList &itemList, m_selectionIndicator.show(); m_resizeIndicator.show(); + m_rotationIndicator.show(); m_anchorIndicator.show(); m_bindingIndicator.show(); m_movingItems.clear(); @@ -266,6 +279,7 @@ void MoveTool::selectedItemsChanged(const QList &itemList) { m_selectionIndicator.setItems(movingItems(itemList)); m_resizeIndicator.setItems(itemList); + m_rotationIndicator.setItems(itemList); m_anchorIndicator.setItems(itemList); m_bindingIndicator.setItems(itemList); updateMoveManipulator(); @@ -386,6 +400,7 @@ void MoveTool::formEditorItemsChanged(const QList &itemList) m_selectionIndicator.updateItems(selectedItemList); m_resizeIndicator.updateItems(selectedItemList); + m_rotationIndicator.updateItems(selectedItemList); m_anchorIndicator.updateItems(selectedItemList); m_bindingIndicator.updateItems(selectedItemList); m_contentNotEditableIndicator.updateItems(selectedItemList); diff --git a/src/plugins/qmldesigner/components/formeditor/movetool.h b/src/plugins/qmldesigner/components/formeditor/movetool.h index 531dab2189b..4e452814a1f 100644 --- a/src/plugins/qmldesigner/components/formeditor/movetool.h +++ b/src/plugins/qmldesigner/components/formeditor/movetool.h @@ -28,6 +28,7 @@ #include "movemanipulator.h" #include "selectionindicator.h" #include "resizeindicator.h" +#include "rotationindicator.h" #include "anchorindicator.h" #include "bindingindicator.h" #include "contentnoteditableindicator.h" @@ -84,6 +85,7 @@ private: MoveManipulator m_moveManipulator; SelectionIndicator m_selectionIndicator; ResizeIndicator m_resizeIndicator; + RotationIndicator m_rotationIndicator; AnchorIndicator m_anchorIndicator; BindingIndicator m_bindingIndicator; ContentNotEditableIndicator m_contentNotEditableIndicator; diff --git a/src/plugins/qmldesigner/components/formeditor/resizeindicator.cpp b/src/plugins/qmldesigner/components/formeditor/resizeindicator.cpp index bc4c95232c0..f9debd05573 100644 --- a/src/plugins/qmldesigner/components/formeditor/resizeindicator.cpp +++ b/src/plugins/qmldesigner/components/formeditor/resizeindicator.cpp @@ -57,7 +57,7 @@ void ResizeIndicator::clear() m_itemControllerHash.clear(); } -bool itemIsResizable(const QmlItemNode &qmlItemNode) +static bool itemIsResizable(const QmlItemNode &qmlItemNode) { return qmlItemNode.isValid() && qmlItemNode.instanceIsResizable() diff --git a/src/plugins/qmldesigner/components/formeditor/resizetool.cpp b/src/plugins/qmldesigner/components/formeditor/resizetool.cpp index e959a4b6b1e..8faab0e94f9 100644 --- a/src/plugins/qmldesigner/components/formeditor/resizetool.cpp +++ b/src/plugins/qmldesigner/components/formeditor/resizetool.cpp @@ -38,11 +38,12 @@ namespace QmlDesigner { ResizeTool::ResizeTool(FormEditorView *editorView) - : AbstractFormEditorTool(editorView), - m_selectionIndicator(editorView->scene()->manipulatorLayerItem()), - m_resizeIndicator(editorView->scene()->manipulatorLayerItem()), - m_anchorIndicator(editorView->scene()->manipulatorLayerItem()), - m_resizeManipulator(editorView->scene()->manipulatorLayerItem(), editorView) + : AbstractFormEditorTool(editorView) + , m_selectionIndicator(editorView->scene()->manipulatorLayerItem()) + , m_resizeIndicator(editorView->scene()->manipulatorLayerItem()) + , m_anchorIndicator(editorView->scene()->manipulatorLayerItem()) + , m_rotationIndicator(editorView->scene()->manipulatorLayerItem()) + , m_resizeManipulator(editorView->scene()->manipulatorLayerItem(), editorView) { } @@ -61,6 +62,7 @@ void ResizeTool::mousePressEvent(const QList &itemList, m_resizeManipulator.begin(event->scenePos()); m_resizeIndicator.hide(); m_anchorIndicator.hide(); + m_rotationIndicator.hide(); } } @@ -113,6 +115,7 @@ void ResizeTool::mouseReleaseEvent(const QList &itemList, m_selectionIndicator.show(); m_resizeIndicator.show(); m_anchorIndicator.show(); + m_rotationIndicator.show(); m_resizeManipulator.end(generateUseSnapping(event->modifiers())); } @@ -171,6 +174,7 @@ void ResizeTool::selectedItemsChanged(const QList & /*itemList* m_selectionIndicator.setItems(items()); m_resizeIndicator.setItems(items()); m_anchorIndicator.setItems(items()); + m_rotationIndicator.setItems(items()); } void ResizeTool::clear() @@ -178,6 +182,7 @@ void ResizeTool::clear() m_selectionIndicator.clear(); m_resizeIndicator.clear(); m_anchorIndicator.clear(); + m_rotationIndicator.clear(); m_resizeManipulator.clear(); } @@ -188,6 +193,7 @@ void ResizeTool::formEditorItemsChanged(const QList &itemList) m_selectionIndicator.updateItems(selectedItemList); m_resizeIndicator.updateItems(selectedItemList); m_anchorIndicator.updateItems(selectedItemList); + m_rotationIndicator.updateItems(selectedItemList); } void ResizeTool::instancesCompleted(const QList &/*itemList*/) diff --git a/src/plugins/qmldesigner/components/formeditor/resizetool.h b/src/plugins/qmldesigner/components/formeditor/resizetool.h index 3ebe5dce4bb..b97d816157d 100644 --- a/src/plugins/qmldesigner/components/formeditor/resizetool.h +++ b/src/plugins/qmldesigner/components/formeditor/resizetool.h @@ -28,6 +28,7 @@ #include "selectionindicator.h" #include "resizeindicator.h" #include "anchorindicator.h" +#include "rotationindicator.h" #include "resizemanipulator.h" namespace QmlDesigner { @@ -69,6 +70,7 @@ private: SelectionIndicator m_selectionIndicator; ResizeIndicator m_resizeIndicator; AnchorIndicator m_anchorIndicator; + RotationIndicator m_rotationIndicator; ResizeManipulator m_resizeManipulator; }; diff --git a/src/plugins/qmldesigner/components/formeditor/rotationcontroller.cpp b/src/plugins/qmldesigner/components/formeditor/rotationcontroller.cpp new file mode 100644 index 00000000000..a1869a5bd94 --- /dev/null +++ b/src/plugins/qmldesigner/components/formeditor/rotationcontroller.cpp @@ -0,0 +1,244 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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 "rotationcontroller.h" + +#include "formeditoritem.h" +#include "layeritem.h" + +#include +#include +#include + +#include +#include + +namespace QmlDesigner { + +class RotationControllerData +{ +public: + RotationControllerData(LayerItem *layerItem, + FormEditorItem *formEditorItem); + RotationControllerData(const RotationControllerData &other); + ~RotationControllerData(); + + + QPointer layerItem; + FormEditorItem *formEditorItem = nullptr; + QSharedPointer topLeftItem; + QSharedPointer topRightItem; + QSharedPointer bottomLeftItem; + QSharedPointer bottomRightItem; +}; + +RotationControllerData::RotationControllerData(LayerItem *layerItem, FormEditorItem *formEditorItem) + : layerItem(layerItem) + , formEditorItem(formEditorItem) +{ + +} + +RotationControllerData::RotationControllerData(const RotationControllerData &other) = default; + +RotationControllerData::~RotationControllerData() +{ + if (layerItem) { + QGraphicsScene *scene = layerItem->scene(); + scene->removeItem(topLeftItem.data()); + scene->removeItem(topRightItem.data()); + scene->removeItem(bottomLeftItem.data()); + scene->removeItem(bottomRightItem.data()); + } +} + + +RotationController::RotationController() + : m_data(new RotationControllerData(nullptr, nullptr)) +{ + +} + +RotationController::RotationController(const QSharedPointer &data) + : m_data(data) +{ + +} + +RotationController::RotationController(LayerItem *layerItem, FormEditorItem *formEditorItem) + : m_data(new RotationControllerData(layerItem, formEditorItem)) +{ + QCursor rotationCursor = getRotationCursor(); + + m_data->topLeftItem = QSharedPointer(new RotationHandleItem(layerItem, *this)); + m_data->topLeftItem->setZValue(302); + m_data->topLeftItem->setCursor(rotationCursor); + + m_data->topRightItem = QSharedPointer(new RotationHandleItem(layerItem, *this)); + m_data->topRightItem->setZValue(301); + m_data->topRightItem->setCursor(rotationCursor); + + m_data->bottomLeftItem = QSharedPointer(new RotationHandleItem(layerItem, *this)); + m_data->bottomLeftItem->setZValue(301); + m_data->bottomLeftItem->setCursor(rotationCursor); + + m_data->bottomRightItem = QSharedPointer(new RotationHandleItem(layerItem, *this)); + m_data->bottomRightItem->setZValue(305); + m_data->bottomRightItem->setCursor(rotationCursor); + + updatePosition(); +} + +RotationController::RotationController(const RotationController &other) = default; + +RotationController::RotationController(const WeakRotationController &rotationController) + : m_data(rotationController.m_data.toStrongRef()) +{ +} + +RotationController::~RotationController() = default; + +RotationController &RotationController::operator =(const RotationController &other) +{ + if (this != &other) + m_data = other.m_data; + return *this; +} + + +bool RotationController::isValid() const +{ + return m_data->formEditorItem && m_data->formEditorItem->qmlItemNode().isValid(); +} + +void RotationController::show() +{ + m_data->topLeftItem->show(); + m_data->topRightItem->show(); + m_data->bottomLeftItem->show(); + m_data->bottomRightItem->show(); +} +void RotationController::hide() +{ + m_data->topLeftItem->hide(); + m_data->topRightItem->hide(); + m_data->bottomLeftItem->hide(); + m_data->bottomRightItem->hide(); +} + + +void RotationController::updatePosition() +{ + if (isValid()) { + + QRectF boundingRect = m_data->formEditorItem->qmlItemNode().instanceBoundingRect(); + QPointF topLeftPointInLayerSpace(m_data->formEditorItem->mapToItem(m_data->layerItem.data(), + boundingRect.topLeft())); + QPointF topRightPointInLayerSpace(m_data->formEditorItem->mapToItem(m_data->layerItem.data(), + boundingRect.topRight())); + QPointF bottomLeftPointInLayerSpace(m_data->formEditorItem->mapToItem(m_data->layerItem.data(), + boundingRect.bottomLeft())); + QPointF bottomRightPointInLayerSpace(m_data->formEditorItem->mapToItem(m_data->layerItem.data(), + boundingRect.bottomRight())); + + const qreal rotation = m_data->formEditorItem->qmlItemNode().rotation(); + + m_data->topRightItem->setHandlePosition(topRightPointInLayerSpace, boundingRect.topRight(), rotation); + m_data->topLeftItem->setHandlePosition(topLeftPointInLayerSpace, boundingRect.topLeft(), rotation); + m_data->bottomLeftItem->setHandlePosition(bottomLeftPointInLayerSpace, boundingRect.bottomLeft(), rotation); + m_data->bottomRightItem->setHandlePosition(bottomRightPointInLayerSpace, boundingRect.bottomRight(), rotation); + } +} + + +FormEditorItem* RotationController::formEditorItem() const +{ + return m_data->formEditorItem; +} + +bool RotationController::isTopLeftHandle(const RotationHandleItem *handle) const +{ + return handle == m_data->topLeftItem; +} + +bool RotationController::isTopRightHandle(const RotationHandleItem *handle) const +{ + return handle == m_data->topRightItem; +} + +bool RotationController::isBottomLeftHandle(const RotationHandleItem *handle) const +{ + return handle == m_data->bottomLeftItem; +} + +bool RotationController::isBottomRightHandle(const RotationHandleItem *handle) const +{ + return handle == m_data->bottomRightItem; +} + +QCursor RotationController::getRotationCursor() const +{ + const QString fontName = "qtds_propertyIconFont.ttf"; + const int cursorSize = 32; //32 is cursor recommended size + + QIcon rotationIcon = Utils::StyleHelper::getIconFromIconFont( + fontName, + Theme::getIconUnicode(Theme::rotation), + cursorSize, cursorSize, + Qt::white); + + return QCursor(rotationIcon.pixmap(cursorSize, cursorSize)); +} + +WeakRotationController RotationController::toWeakRotationController() const +{ + return WeakRotationController(*this); +} + +WeakRotationController::WeakRotationController() = default; + +WeakRotationController::WeakRotationController(const WeakRotationController &rotationController) = default; + +WeakRotationController::WeakRotationController(const RotationController &rotationController) + : m_data(rotationController.m_data.toWeakRef()) +{ +} + +WeakRotationController::~WeakRotationController() = default; + +WeakRotationController &WeakRotationController::operator =(const WeakRotationController &other) +{ + if (m_data != other.m_data) + m_data = other.m_data; + + return *this; +} + +RotationController WeakRotationController::toRotationController() const +{ + return RotationController(*this); +} + +} diff --git a/src/plugins/qmldesigner/components/formeditor/rotationcontroller.h b/src/plugins/qmldesigner/components/formeditor/rotationcontroller.h new file mode 100644 index 00000000000..52b50023872 --- /dev/null +++ b/src/plugins/qmldesigner/components/formeditor/rotationcontroller.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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. +** +****************************************************************************/ +#pragma once + +#include +#include + +namespace QmlDesigner { + +class FormEditorItem; +class LayerItem; +class RotationHandleItem; +class RotationControllerData; +class WeakRotationController; + +class RotationController +{ + friend class WeakRotationController; +public: + RotationController(); + RotationController(LayerItem *layerItem, FormEditorItem *formEditorItem); + RotationController(const RotationController &rotationController); + RotationController(const WeakRotationController &rotationController); + ~RotationController(); + + RotationController& operator=(const RotationController &other); + + void show(); + void hide(); + + void updatePosition(); + + bool isValid() const; + + FormEditorItem *formEditorItem() const; + + bool isTopLeftHandle(const RotationHandleItem *handle) const; + bool isTopRightHandle(const RotationHandleItem *handle) const; + bool isBottomLeftHandle(const RotationHandleItem *handle) const; + bool isBottomRightHandle(const RotationHandleItem *handle) const; + + WeakRotationController toWeakRotationController() const; + +private: + RotationController(const QSharedPointer &data); + + QCursor getRotationCursor() const; + +private: + QSharedPointer m_data; +}; + +class WeakRotationController +{ + friend class RotationController; +public: + WeakRotationController(); + WeakRotationController(const WeakRotationController &rotationController); + WeakRotationController(const RotationController &rotationController); + ~WeakRotationController(); + + WeakRotationController& operator=(const WeakRotationController &other); + + RotationController toRotationController() const; + +private: // variables + QWeakPointer m_data; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/formeditor/rotationhandleitem.cpp b/src/plugins/qmldesigner/components/formeditor/rotationhandleitem.cpp new file mode 100644 index 00000000000..2863b1e194d --- /dev/null +++ b/src/plugins/qmldesigner/components/formeditor/rotationhandleitem.cpp @@ -0,0 +1,119 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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 "rotationhandleitem.h" + +#include +#include + +namespace QmlDesigner { + +RotationHandleItem::RotationHandleItem(QGraphicsItem *parent, const RotationController &rotationController) + : QGraphicsItem(parent) + , m_weakRotationController(rotationController.toWeakRotationController()) +{ + setFlag(QGraphicsItem::ItemIsMovable, true); + setFlag(QGraphicsItem::ItemIgnoresTransformations, true); + setAcceptedMouseButtons(Qt::NoButton); +} + +RotationHandleItem::~RotationHandleItem() = default; + +void RotationHandleItem::setHandlePosition(const QPointF & globalPosition, const QPointF & itemSpacePosition, const qreal rotation) +{ + m_itemSpacePosition = itemSpacePosition; + setRotation(rotation); + setPos(globalPosition); +} + +QRectF RotationHandleItem::boundingRect() const +{ + QRectF rectangle; + + if (isTopLeftHandle()) { + rectangle = { -30., -30., 27., 27.}; + } + else if (isTopRightHandle()) { + rectangle = { 3., -30., 27., 27.}; + } + else if (isBottomLeftHandle()) { + rectangle = { -30., 3., 27., 27.}; + } + else if (isBottomRightHandle()) { + rectangle = { 3., 3., 27., 27.}; + } + return rectangle; +} + +void RotationHandleItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * /* option */, QWidget * /* widget */) +{ + painter->save(); + QPen pen = painter->pen(); + pen.setWidth(1); + pen.setCosmetic(true); + painter->setPen(pen); + painter->setRenderHint(QPainter::Antialiasing, false); + painter->setBrush(QColor(255, 255, 255)); + painter->drawRect(QRectF(-3., -3., 5., 5.)); + + painter->restore(); +} + + +RotationController RotationHandleItem::rotationController() const +{ + return RotationController(m_weakRotationController.toRotationController()); +} + +RotationHandleItem* RotationHandleItem::fromGraphicsItem(QGraphicsItem *item) +{ + return qgraphicsitem_cast(item); +} + +bool RotationHandleItem::isTopLeftHandle() const +{ + return rotationController().isTopLeftHandle(this); +} + +bool RotationHandleItem::isTopRightHandle() const +{ + return rotationController().isTopRightHandle(this); +} + +bool RotationHandleItem::isBottomLeftHandle() const +{ + return rotationController().isBottomLeftHandle(this); +} + +bool RotationHandleItem::isBottomRightHandle() const +{ + return rotationController().isBottomRightHandle(this); +} + +QPointF RotationHandleItem::itemSpacePosition() const +{ + return m_itemSpacePosition; +} +} diff --git a/src/plugins/qmldesigner/components/formeditor/rotationhandleitem.h b/src/plugins/qmldesigner/components/formeditor/rotationhandleitem.h new file mode 100644 index 00000000000..662a005f880 --- /dev/null +++ b/src/plugins/qmldesigner/components/formeditor/rotationhandleitem.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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. +** +****************************************************************************/ +#pragma once + +#include "rotationcontroller.h" + +#include + +#include + +namespace QmlDesigner { + +class QMLDESIGNERCORE_EXPORT RotationHandleItem : public QGraphicsItem +{ +public: + enum + { + Type = 0xEBEB + }; + + RotationHandleItem(QGraphicsItem *parent, const RotationController &rotationController); + ~RotationHandleItem() override; + void setHandlePosition(const QPointF & globalPosition, const QPointF & itemSpacePosition, const qreal rotation); + + int type() const override; + QRectF boundingRect() const override; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; + + RotationController rotationController() const; + + static RotationHandleItem* fromGraphicsItem(QGraphicsItem *item); + + bool isTopLeftHandle() const; + bool isTopRightHandle() const; + bool isBottomLeftHandle() const; + bool isBottomRightHandle() const; + + QPointF itemSpacePosition() const; + +private: + WeakRotationController m_weakRotationController; + QPointF m_itemSpacePosition; +}; + +inline int RotationHandleItem::type() const +{ + return Type; +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/formeditor/rotationindicator.cpp b/src/plugins/qmldesigner/components/formeditor/rotationindicator.cpp new file mode 100644 index 00000000000..a47aa0ec907 --- /dev/null +++ b/src/plugins/qmldesigner/components/formeditor/rotationindicator.cpp @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** 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 "rotationindicator.h" + +#include "formeditoritem.h" + +namespace QmlDesigner { + +RotationIndicator::RotationIndicator(LayerItem *layerItem) + : m_layerItem(layerItem) +{ + Q_ASSERT(layerItem); +} + +RotationIndicator::~RotationIndicator() +{ + m_itemControllerHash.clear(); +} + +void RotationIndicator::show() +{ + for (RotationController controller : m_itemControllerHash) + controller.show(); +} + +void RotationIndicator::hide() +{ + for (RotationController controller : m_itemControllerHash) + controller.hide(); +} + +void RotationIndicator::clear() +{ + m_itemControllerHash.clear(); +} + +static bool itemIsRotatable(const QmlItemNode &qmlItemNode) +{ + return qmlItemNode.isValid() + && qmlItemNode.instanceIsResizable() + && qmlItemNode.modelIsMovable() + && qmlItemNode.modelIsRotatable() + && !qmlItemNode.instanceIsInLayoutable(); +} + +void RotationIndicator::setItems(const QList &itemList) +{ + clear(); + + for (FormEditorItem *item : itemList) { + if (item && itemIsRotatable(item->qmlItemNode())) { + RotationController controller(m_layerItem, item); + m_itemControllerHash.insert(item, controller); + } + } +} + +void RotationIndicator::updateItems(const QList &itemList) +{ + for (FormEditorItem *item : itemList) { + if (m_itemControllerHash.contains(item)) { + if (!item || !itemIsRotatable(item->qmlItemNode())) { + m_itemControllerHash.take(item); + } else { + RotationController controller(m_itemControllerHash.value(item)); + controller.updatePosition(); + } + } else if (item && itemIsRotatable(item->qmlItemNode())) { + RotationController controller(m_layerItem, item); + m_itemControllerHash.insert(item, controller); + } + } +} + +} diff --git a/src/plugins/qmldesigner/components/formeditor/rotationindicator.h b/src/plugins/qmldesigner/components/formeditor/rotationindicator.h new file mode 100644 index 00000000000..abe998fb1d3 --- /dev/null +++ b/src/plugins/qmldesigner/components/formeditor/rotationindicator.h @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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. +** +****************************************************************************/ +#pragma once + +#include "rotationcontroller.h" + +#include +#include + +namespace QmlDesigner { + +class FormEditorItem; +class LayerItem; + +class RotationIndicator +{ +public: + enum Orientation { + Top = 1, + Right = 2, + Bottom = 4, + Left = 8 + }; + + explicit RotationIndicator(LayerItem *layerItem); + ~RotationIndicator(); + + void show(); + void hide(); + void clear(); + + void setItems(const QList &itemList); + void updateItems(const QList &itemList); + +private: + QHash m_itemControllerHash; + LayerItem *m_layerItem; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/formeditor/rotationmanipulator.cpp b/src/plugins/qmldesigner/components/formeditor/rotationmanipulator.cpp new file mode 100644 index 00000000000..a7dbe75418b --- /dev/null +++ b/src/plugins/qmldesigner/components/formeditor/rotationmanipulator.cpp @@ -0,0 +1,260 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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 "rotationmanipulator.h" + +#include "formeditoritem.h" +#include "formeditorscene.h" +#include "qmlanchors.h" +#include +#include +#include "enumeration.h" +#include "mathutils.h" + +#include + +namespace QmlDesigner { + +RotationManipulator::RotationManipulator(LayerItem *layerItem, FormEditorView *view) + : m_view(view) + , m_beginTopMargin(0.0) + , m_beginLeftMargin(0.0) + , m_beginRightMargin(0.0) + , m_beginBottomMargin(0.0) + , m_layerItem(layerItem) +{ +} + +RotationManipulator::~RotationManipulator() +{ + deleteSnapLines(); +} + +void RotationManipulator::setHandle(RotationHandleItem *rotationHandle) +{ + Q_ASSERT(rotationHandle); + m_rotationHandle = rotationHandle; + m_rotationController = rotationHandle->rotationController(); + Q_ASSERT(m_rotationController.isValid()); +} + +void RotationManipulator::removeHandle() +{ + m_rotationController = RotationController(); + m_rotationHandle = nullptr; +} + +void RotationManipulator::begin(const QPointF &/*beginPoint*/) +{ + if (m_rotationController.isValid()) { + m_isActive = true; + m_beginBoundingRect = m_rotationController.formEditorItem()->qmlItemNode().instanceBoundingRect(); + m_beginFromContentItemToSceneTransform = m_rotationController.formEditorItem()->instanceSceneContentItemTransform(); + m_beginFromSceneToContentItemTransform = m_beginFromContentItemToSceneTransform.inverted(); + m_beginFromItemToSceneTransform = m_rotationController.formEditorItem()->instanceSceneTransform(); + m_beginToParentTransform = m_rotationController.formEditorItem()->qmlItemNode().instanceTransform(); + m_rewriterTransaction = m_view->beginRewriterTransaction(QByteArrayLiteral("RotationManipulator::begin")); + m_rewriterTransaction.ignoreSemanticChecks(); + m_beginBottomRightPoint = m_beginToParentTransform.map(m_rotationController.formEditorItem()->qmlItemNode().instanceBoundingRect().bottomRight()); + + QmlAnchors anchors(m_rotationController.formEditorItem()->qmlItemNode().anchors()); + m_beginTopMargin = anchors.instanceMargin(AnchorLineTop); + m_beginLeftMargin = anchors.instanceMargin(AnchorLineLeft); + m_beginRightMargin = anchors.instanceMargin(AnchorLineRight); + m_beginBottomMargin = anchors.instanceMargin(AnchorLineBottom); + + m_beginRotation = m_rotationController.formEditorItem()->qmlItemNode().rotation(); + deleteSnapLines(); + } +} + +void RotationManipulator::update(const QPointF& updatePoint, Qt::KeyboardModifiers keyMods) +{ + if (m_rotationController.isValid()) { + FormEditorItem *formEditorItem = m_rotationController.formEditorItem(); + + const bool degreeStep5 = keyMods.testFlag(Qt::ShiftModifier); + const bool degreeStep45 = keyMods.testFlag(Qt::AltModifier); + + QPointF updatePointInLocalSpace = m_beginFromSceneToContentItemTransform.map(updatePoint); + + QRectF boundingRect(m_beginBoundingRect); + + QPointF transformOrigin; + QVariant transformOriginVar = formEditorItem->qmlItemNode().transformOrigin(); + if (transformOriginVar.isValid()) { + QmlDesigner::Enumeration a = transformOriginVar.value(); + const QString originStr = a.nameToString(); + if (originStr == "TopLeft") { + transformOrigin = boundingRect.topLeft(); + } + else if (originStr == "Top") { + transformOrigin.setX(boundingRect.center().x()); + transformOrigin.setY(boundingRect.top()); + } + else if (originStr == "TopRight") { + transformOrigin = boundingRect.topRight(); + } + else if (originStr == "Right") { + transformOrigin.setX(boundingRect.right()); + transformOrigin.setY(boundingRect.center().y()); + } + else if (originStr == "BottomRight") { + transformOrigin = boundingRect.bottomRight(); + } + else if (originStr == "Bottom") { + transformOrigin.setX(boundingRect.center().x()); + transformOrigin.setY(boundingRect.bottom()); + } + else if (originStr == "BottomLeft") { + transformOrigin = boundingRect.bottomLeft(); + } + else if (originStr == "Left") { + transformOrigin.setX(boundingRect.left()); + transformOrigin.setY(boundingRect.center().y()); + } + else { + //center and anything else + transformOrigin = boundingRect.center(); + } + } + else { + transformOrigin = boundingRect.center(); + } + + auto angleCalc = [](const QPointF &origin, const QPointF &position){ + const qreal deltaX = origin.x() - position.x(); + const qreal deltaY = origin.y() - position.y(); + + return qRadiansToDegrees(qAtan2(deltaY, deltaX)); + }; + + auto snapCalc = [](const qreal angle, const qreal snap){ + return qRound(angle/snap)*snap; + }; + + auto resultCalc = [&](qreal cursorAngle, qreal handleAngle) { + qreal result = 0.0; + const qreal rotateBy = cursorAngle - handleAngle; + + if (degreeStep45) + result = snapCalc(rotateBy, 45.0); + else if (degreeStep5) + result = snapCalc(rotateBy, 5.0); + else + result = rotateBy; + + while (result > 360.) + result -= 360.; + while (result < -360.) + result += 360.; + + return result; + }; + + const qreal cursorAngle = angleCalc(transformOrigin, updatePointInLocalSpace); + const QPointF topLeftHandle = boundingRect.topLeft(); + const QPointF topRightHandle = boundingRect.topRight(); + const QPointF bottomRightHandle = boundingRect.bottomRight(); + const QPointF bottomLeftHandle = boundingRect.bottomLeft(); + + if (m_rotationHandle->isTopLeftHandle()) { + //we need to change origin coords, otherwise item will rotate unpredictably: + if (transformOrigin == topLeftHandle) + transformOrigin = boundingRect.center(); + + const qreal handleAngle = angleCalc(transformOrigin, topLeftHandle); + formEditorItem->qmlItemNode().setRotation(resultCalc(cursorAngle + m_beginRotation, handleAngle)); + } + else if (m_rotationHandle->isTopRightHandle()) { + if (transformOrigin == topRightHandle) + transformOrigin = boundingRect.center(); + const qreal handleAngle = angleCalc(transformOrigin, topRightHandle); + formEditorItem->qmlItemNode().setRotation(resultCalc(cursorAngle + m_beginRotation, handleAngle)); + } + else if (m_rotationHandle->isBottomRightHandle()) { + if (transformOrigin == bottomRightHandle) + transformOrigin = boundingRect.center(); + const qreal handleAngle = angleCalc(transformOrigin, bottomRightHandle); + formEditorItem->qmlItemNode().setRotation(resultCalc(cursorAngle + m_beginRotation, handleAngle)); + } + else if (m_rotationHandle->isBottomLeftHandle()) { + if (transformOrigin == bottomLeftHandle) + transformOrigin = boundingRect.center(); + const qreal handleAngle = angleCalc(transformOrigin, bottomLeftHandle); + formEditorItem->qmlItemNode().setRotation(resultCalc(cursorAngle + m_beginRotation, handleAngle)); + } + } +} + +void RotationManipulator::end() +{ + m_isActive = false; + m_rewriterTransaction.commit(); + clear(); + removeHandle(); +} + +void RotationManipulator::deleteSnapLines() +{ + if (m_layerItem) { + foreach (QGraphicsItem *item, m_graphicsLineList) { + m_layerItem->scene()->removeItem(item); + delete item; + } + } + + m_graphicsLineList.clear(); + m_view->scene()->update(); +} + +RotationHandleItem *RotationManipulator::rotationHandle() +{ + return m_rotationHandle; +} + +void RotationManipulator::clear() +{ + m_rewriterTransaction.commit(); + + deleteSnapLines(); + m_beginBoundingRect = QRectF(); + m_beginFromSceneToContentItemTransform = QTransform(); + m_beginFromContentItemToSceneTransform = QTransform(); + m_beginFromItemToSceneTransform = QTransform(); + m_beginToParentTransform = QTransform(); + m_beginTopMargin = 0.0; + m_beginLeftMargin = 0.0; + m_beginRightMargin = 0.0; + m_beginBottomMargin = 0.0; + removeHandle(); +} + +bool RotationManipulator::isActive() const +{ + return m_isActive; +} + +} diff --git a/src/plugins/qmldesigner/components/formeditor/rotationmanipulator.h b/src/plugins/qmldesigner/components/formeditor/rotationmanipulator.h new file mode 100644 index 00000000000..84ad771a7f7 --- /dev/null +++ b/src/plugins/qmldesigner/components/formeditor/rotationmanipulator.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ +#pragma once + +#include "rotationhandleitem.h" +#include +#include "rewritertransaction.h" +#include "formeditorview.h" + +#include + +namespace QmlDesigner { + +class RotationHandleItem; +class Model; + +class RotationManipulator +{ +public: + RotationManipulator(LayerItem *layerItem, FormEditorView *view); + ~RotationManipulator(); + + void setHandle(RotationHandleItem *rotationHandle); + void removeHandle(); + + void begin(const QPointF& beginPoint); + void update(const QPointF& updatePoint, Qt::KeyboardModifiers keyMods = Qt::NoModifier); + void end(); + + void clear(); + + bool isActive() const; + +protected: + void deleteSnapLines(); + RotationHandleItem *rotationHandle(); + +private: + QPointer m_view; + QList m_graphicsLineList; + RotationController m_rotationController; // hold the controller so that the handle cant be deleted + QTransform m_beginFromSceneToContentItemTransform; + QTransform m_beginFromContentItemToSceneTransform; + QTransform m_beginFromItemToSceneTransform; + QTransform m_beginToParentTransform; + QRectF m_beginBoundingRect; + QPointF m_beginBottomRightPoint; + double m_beginTopMargin; + double m_beginLeftMargin; + double m_beginRightMargin; + double m_beginBottomMargin; + QPointer m_layerItem; + RotationHandleItem *m_rotationHandle = nullptr; + RewriterTransaction m_rewriterTransaction; + bool m_isActive = false; + + qreal m_beginRotation; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/formeditor/rotationtool.cpp b/src/plugins/qmldesigner/components/formeditor/rotationtool.cpp new file mode 100644 index 00000000000..d9102a6c8d2 --- /dev/null +++ b/src/plugins/qmldesigner/components/formeditor/rotationtool.cpp @@ -0,0 +1,203 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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 "rotationtool.h" + +#include "formeditorscene.h" +#include "formeditorview.h" +#include "formeditorwidget.h" + +#include "rotationhandleitem.h" + +#include +#include +#include + +namespace QmlDesigner { + +RotationTool::RotationTool(FormEditorView *editorView) + : AbstractFormEditorTool(editorView) + , m_selectionIndicator(editorView->scene()->manipulatorLayerItem()) + , m_rotationIndicator(editorView->scene()->manipulatorLayerItem()) + , m_anchorIndicator(editorView->scene()->manipulatorLayerItem()) + , m_rotationManipulator(editorView->scene()->manipulatorLayerItem(), editorView) +{ +} + +RotationTool::~RotationTool() = default; + + +void RotationTool::mousePressEvent(const QList &itemList, + QGraphicsSceneMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { + if (itemList.isEmpty()) + return; + + RotationHandleItem *rotationHandle = RotationHandleItem::fromGraphicsItem(itemList.constFirst()); + if (rotationHandle && rotationHandle->rotationController().isValid()) { + m_rotationManipulator.setHandle(rotationHandle); + m_rotationManipulator.begin(event->scenePos()); + m_rotationIndicator.hide(); + m_anchorIndicator.hide(); + } + } + + AbstractFormEditorTool::mousePressEvent(itemList, event); +} + +void RotationTool::mouseMoveEvent(const QList &, + QGraphicsSceneMouseEvent *event) +{ + if (m_rotationManipulator.isActive()) + m_rotationManipulator.update(event->scenePos(), event->modifiers()); +} + +void RotationTool::hoverMoveEvent(const QList &itemList, + QGraphicsSceneMouseEvent * /*event*/) +{ + if (itemList.isEmpty()) { + view()->changeToSelectionTool(); + return; + } + + RotationHandleItem* rotationHandle = RotationHandleItem::fromGraphicsItem(itemList.constFirst()); + if (rotationHandle && rotationHandle->rotationController().isValid()) { + m_rotationManipulator.setHandle(rotationHandle); + } else { + view()->changeToSelectionTool(); + return; + } +} + +void RotationTool::dragLeaveEvent(const QList &/*itemList*/, QGraphicsSceneDragDropEvent * /*event*/) +{ + +} + +void RotationTool::dragMoveEvent(const QList &/*itemList*/, QGraphicsSceneDragDropEvent * /*event*/) +{ + +} + + +void RotationTool::mouseReleaseEvent(const QList &itemList, + QGraphicsSceneMouseEvent *event) +{ + if (m_rotationManipulator.isActive()) { + if (itemList.isEmpty()) + return; + + m_selectionIndicator.show(); + m_rotationIndicator.show(); + m_anchorIndicator.show(); + m_rotationManipulator.end(); + } + + AbstractFormEditorTool::mouseReleaseEvent(itemList, event); +} + +void RotationTool::mouseDoubleClickEvent(const QList & /*itemList*/, + QGraphicsSceneMouseEvent * /*event*/) +{ +} + +void RotationTool::keyPressEvent(QKeyEvent * event) +{ + switch (event->key()) { + case Qt::Key_Shift: + case Qt::Key_Alt: + case Qt::Key_Control: + case Qt::Key_AltGr: + event->setAccepted(false); + return; + } + + double moveStep = 1.0; + + if (event->modifiers().testFlag(Qt::ShiftModifier)) + moveStep = 10.0; +} + +void RotationTool::keyReleaseEvent(QKeyEvent * keyEvent) +{ + switch (keyEvent->key()) { + case Qt::Key_Shift: + case Qt::Key_Alt: + case Qt::Key_Control: + case Qt::Key_AltGr: + keyEvent->setAccepted(false); + return; + } +} + +void RotationTool::itemsAboutToRemoved(const QList & /*itemList*/) +{ + +} + +void RotationTool::selectedItemsChanged(const QList & /*itemList*/) +{ + m_selectionIndicator.setItems(items()); + m_rotationIndicator.setItems(items()); + m_anchorIndicator.setItems(items()); +} + +void RotationTool::clear() +{ + m_selectionIndicator.clear(); + m_rotationIndicator.clear(); + m_anchorIndicator.clear(); + m_rotationManipulator.clear(); +} + +void RotationTool::formEditorItemsChanged(const QList &itemList) +{ + const QList selectedItemList = filterSelectedModelNodes(itemList); + + m_selectionIndicator.updateItems(selectedItemList); + m_rotationIndicator.updateItems(selectedItemList); + m_anchorIndicator.updateItems(selectedItemList); +} + +void RotationTool::instancesCompleted(const QList &/*itemList*/) +{ +} + +void RotationTool::instancePropertyChange(const QList > & /*propertyList*/) +{ +} + +void RotationTool::focusLost() +{ +} + + +void RotationTool::instancesParentChanged(const QList &/*itemList*/) +{ + +} + +} //namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/formeditor/rotationtool.h b/src/plugins/qmldesigner/components/formeditor/rotationtool.h new file mode 100644 index 00000000000..3d53e2d8732 --- /dev/null +++ b/src/plugins/qmldesigner/components/formeditor/rotationtool.h @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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. +** +****************************************************************************/ +#pragma once + +#include "abstractformeditortool.h" +#include "selectionindicator.h" +#include "rotationindicator.h" +#include "anchorindicator.h" +#include "rotationmanipulator.h" + +namespace QmlDesigner { + +class RotationTool : public AbstractFormEditorTool +{ +public: + RotationTool(FormEditorView* editorView); + ~RotationTool() override; + + void mousePressEvent(const QList &itemList, QGraphicsSceneMouseEvent *event) override; + void mouseMoveEvent(const QList &itemList, QGraphicsSceneMouseEvent *event) override; + void mouseReleaseEvent(const QList &itemList, QGraphicsSceneMouseEvent *event) override; + void mouseDoubleClickEvent(const QList &itemList, QGraphicsSceneMouseEvent *event) override; + + void hoverMoveEvent(const QList &itemList, QGraphicsSceneMouseEvent *event) override; + + void dragLeaveEvent(const QList &itemList, QGraphicsSceneDragDropEvent *event) override; + void dragMoveEvent(const QList &itemList, QGraphicsSceneDragDropEvent *event) override; + + void keyPressEvent(QKeyEvent *event) override; + void keyReleaseEvent(QKeyEvent *keyEvent) override; + + void itemsAboutToRemoved(const QList &itemList) override; + + void selectedItemsChanged(const QList &itemList) override; + + void clear() override; + + void formEditorItemsChanged(const QList &itemList) override; + void instancesParentChanged(const QList &itemList) override; + + void instancesCompleted(const QList &itemList) override; + void instancePropertyChange(const QList > &propertyList) override; + + void focusLost() override; + +private: + SelectionIndicator m_selectionIndicator; + RotationIndicator m_rotationIndicator; + AnchorIndicator m_anchorIndicator; + RotationManipulator m_rotationManipulator; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/formeditor/selectiontool.cpp b/src/plugins/qmldesigner/components/formeditor/selectiontool.cpp index c23e540a4bb..7230e97a1c7 100644 --- a/src/plugins/qmldesigner/components/formeditor/selectiontool.cpp +++ b/src/plugins/qmldesigner/components/formeditor/selectiontool.cpp @@ -30,6 +30,7 @@ #include "formeditorgraphicsview.h" #include "resizehandleitem.h" +#include "rotationhandleitem.h" #include @@ -42,14 +43,15 @@ const int s_startDragDistance = 20; const int s_startDragTime = 50; SelectionTool::SelectionTool(FormEditorView *editorView) - : AbstractFormEditorTool(editorView), - m_rubberbandSelectionManipulator(editorView->scene()->manipulatorLayerItem(), editorView), - m_singleSelectionManipulator(editorView), - m_selectionIndicator(editorView->scene()->manipulatorLayerItem()), - m_resizeIndicator(editorView->scene()->manipulatorLayerItem()), - m_anchorIndicator(editorView->scene()->manipulatorLayerItem()), - m_bindingIndicator(editorView->scene()->manipulatorLayerItem()), - m_contentNotEditableIndicator(editorView->scene()->manipulatorLayerItem()) + : AbstractFormEditorTool(editorView) + , m_rubberbandSelectionManipulator(editorView->scene()->manipulatorLayerItem(), editorView) + , m_singleSelectionManipulator(editorView) + , m_selectionIndicator(editorView->scene()->manipulatorLayerItem()) + , m_resizeIndicator(editorView->scene()->manipulatorLayerItem()) + , m_rotationIndicator(editorView->scene()->manipulatorLayerItem()) + , m_anchorIndicator(editorView->scene()->manipulatorLayerItem()) + , m_bindingIndicator(editorView->scene()->manipulatorLayerItem()) + , m_contentNotEditableIndicator(editorView->scene()->manipulatorLayerItem()) { m_selectionIndicator.setCursor(Qt::ArrowCursor); } @@ -135,6 +137,12 @@ void SelectionTool::hoverMoveEvent(const QList &itemList, return; } + RotationHandleItem* rotationHandle = RotationHandleItem::fromGraphicsItem(itemList.constFirst()); + if (rotationHandle) { + view()->changeToRotationTool(); + return; + } + if ((topSelectedItemIsMovable(itemList) && !view()->hasSingleSelectedModelNode()) || (selectedItemCursorInMovableArea(event->scenePos()) && !event->modifiers().testFlag(Qt::ControlModifier) @@ -245,6 +253,7 @@ void SelectionTool::clear() m_singleSelectionManipulator.clear(); m_selectionIndicator.clear(); m_resizeIndicator.clear(); + m_rotationIndicator.clear(); m_anchorIndicator.clear(); m_bindingIndicator.clear(); m_contentNotEditableIndicator.clear(); @@ -256,6 +265,7 @@ void SelectionTool::selectedItemsChanged(const QList &itemList) { m_selectionIndicator.setItems(itemList); m_resizeIndicator.setItems(itemList); + m_rotationIndicator.setItems(itemList); m_anchorIndicator.setItems(itemList); m_bindingIndicator.setItems(itemList); } @@ -266,6 +276,7 @@ void SelectionTool::formEditorItemsChanged(const QList &itemLis m_selectionIndicator.updateItems(selectedItemList); m_resizeIndicator.updateItems(selectedItemList); + m_rotationIndicator.updateItems(selectedItemList); m_anchorIndicator.updateItems(selectedItemList); m_bindingIndicator.updateItems(selectedItemList); m_contentNotEditableIndicator.updateItems(selectedItemList); diff --git a/src/plugins/qmldesigner/components/formeditor/selectiontool.h b/src/plugins/qmldesigner/components/formeditor/selectiontool.h index c16ef4f30fe..0182a193a41 100644 --- a/src/plugins/qmldesigner/components/formeditor/selectiontool.h +++ b/src/plugins/qmldesigner/components/formeditor/selectiontool.h @@ -29,6 +29,7 @@ #include "singleselectionmanipulator.h" #include "selectionindicator.h" #include "resizeindicator.h" +#include "rotationindicator.h" #include "anchorindicator.h" #include "bindingindicator.h" #include "contentnoteditableindicator.h" @@ -85,6 +86,7 @@ private: SingleSelectionManipulator m_singleSelectionManipulator; SelectionIndicator m_selectionIndicator; ResizeIndicator m_resizeIndicator; + RotationIndicator m_rotationIndicator; AnchorIndicator m_anchorIndicator; BindingIndicator m_bindingIndicator; ContentNotEditableIndicator m_contentNotEditableIndicator; diff --git a/src/plugins/qmldesigner/designercore/include/qmlitemnode.h b/src/plugins/qmldesigner/designercore/include/qmlitemnode.h index 62c5303a31d..c3c01eec814 100644 --- a/src/plugins/qmldesigner/designercore/include/qmlitemnode.h +++ b/src/plugins/qmldesigner/designercore/include/qmlitemnode.h @@ -90,6 +90,7 @@ public: bool modelIsMovable() const; bool modelIsResizable() const; + bool modelIsRotatable() const; bool modelIsInLayout() const; QRectF instanceBoundingRect() const; @@ -123,6 +124,10 @@ public: bool isInLayout() const; bool canBereparentedTo(const ModelNode &potentialParent) const; + void setRotation(const qreal &angle); + qreal rotation() const; + QVariant transformOrigin(); + bool isInStackedContainer() const; bool isFlowView() const; diff --git a/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp b/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp index 12a6092f931..7a1e7d05f4a 100644 --- a/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp +++ b/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp @@ -38,6 +38,7 @@ #include "rewriterview.h" #include "modelmerger.h" #include "rewritingexception.h" +#include "designermcumanager.h" #include #include @@ -286,6 +287,38 @@ bool QmlItemNode::modelIsResizable() const && !modelIsInLayout(); } +static bool isMcuRotationAllowed(QString itemName, bool hasChildren) { + const QString propName = "rotation"; + const DesignerMcuManager &manager = DesignerMcuManager::instance(); + if (manager.isMCUProject()) { + if (manager.allowedItemProperties().contains(itemName)) { + const DesignerMcuManager::ItemProperties properties = + manager.allowedItemProperties().value(itemName); + if (properties.properties.contains(propName)) { + if (hasChildren) + return properties.allowChildren; + return true; + } + } + + if (manager.bannedItems().contains(itemName)) + return false; + + if (manager.bannedProperties().contains(propName)) + return false; + } + + return true; +} + +bool QmlItemNode::modelIsRotatable() const +{ + return !modelNode().hasBindingProperty("rotation") + && itemIsResizable(modelNode()) + && !modelIsInLayout() + && isMcuRotationAllowed(QString::fromUtf8(modelNode().type()), hasChildren()); +} + bool QmlItemNode::modelIsInLayout() const { if (modelNode().hasParentProperty()) { @@ -535,6 +568,30 @@ void QmlItemNode::setSize(const QSizeF &size) setVariantProperty("height", qRound(size.height())); } +void QmlItemNode::setRotation(const qreal &angle) +{ + if (!hasBindingProperty("rotation")) + setVariantProperty("rotation", angle); +} + +qreal QmlItemNode::rotation() const +{ + if (hasProperty("rotation") && !hasBindingProperty("rotation")) { + return modelNode().variantProperty("rotation").value().toReal(); + } + + return 0.0; +} + +QVariant QmlItemNode::transformOrigin() +{ + if (hasProperty("transformOrigin")) { + return modelNode().variantProperty("transformOrigin").value(); + } + + return {}; +} + bool QmlFlowItemNode::isValid() const { return isValidQmlFlowItemNode(modelNode()); diff --git a/src/plugins/qmldesigner/qmldesignerplugin.qbs b/src/plugins/qmldesigner/qmldesignerplugin.qbs index 93cf6587cad..dd7a95c9700 100644 --- a/src/plugins/qmldesigner/qmldesignerplugin.qbs +++ b/src/plugins/qmldesigner/qmldesignerplugin.qbs @@ -548,6 +548,16 @@ Project { "formeditor/resizemanipulator.h", "formeditor/resizetool.cpp", "formeditor/resizetool.h", + "formeditor/rotationtool.cpp", + "formeditor/rotationtool.h", + "formeditor/rotationindicator.cpp", + "formeditor/rotationindicator.h", + "formeditor/rotationcontroller.cpp", + "formeditor/rotationcontroller.h", + "formeditor/rotationhandleitem.cpp", + "formeditor/rotationhandleitem.h", + "formeditor/rotationmanipulator.cpp", + "formeditor/rotationmanipulator.h", "formeditor/rubberbandselectionmanipulator.cpp", "formeditor/rubberbandselectionmanipulator.h", "formeditor/scaleitem.cpp",