Commit fc6d51af authored by Marco Bubke's avatar Marco Bubke
Browse files

QmlDesigner: Support anchoring at snapping



Change-Id: I3ec504e931ee63761538acb4666a3c8ce1a592e5
Reviewed-by: default avatarThomas Hartmann <Thomas.Hartmann@digia.com>
parent 81cbe76c
......@@ -29,6 +29,7 @@
#include "abstractformeditortool.h"
#include "formeditorview.h"
#include "formeditorwidget.h"
#include <modelnodecontextmenu.h>
......@@ -36,6 +37,7 @@
#include <QGraphicsSceneDragDropEvent>
#include <QMimeData>
#include <nodemetainfo.h>
#include <QAction>
namespace QmlDesigner {
......@@ -219,6 +221,22 @@ void AbstractFormEditorTool::showContextMenu(QGraphicsSceneMouseEvent *event)
ModelNodeContextMenu::showContextMenu(view(), event->screenPos(), event->scenePos().toPoint(), true);
}
Snapper::Snapping AbstractFormEditorTool::generateUseSnapping(Qt::KeyboardModifiers keyboardModifier) const
{
bool shouldSnapping = view()->formEditorWidget()->snappingAction()->isChecked();
bool shouldSnappingAndAnchoring = view()->formEditorWidget()->snappingAndAnchoringAction()->isChecked();
Snapper::Snapping useSnapping = Snapper::NoSnapping;
if (keyboardModifier.testFlag(Qt::ControlModifier) != (shouldSnapping || shouldSnappingAndAnchoring)) {
if (shouldSnappingAndAnchoring)
useSnapping = Snapper::UseSnappingAndAnchoring;
else
useSnapping = Snapper::UseSnapping;
}
return useSnapping;
}
void AbstractFormEditorTool::clear()
{
m_itemList.clear();
......
......@@ -34,6 +34,8 @@
#include <qmldesignercorelib_global.h>
#include "snapper.h"
QT_BEGIN_NAMESPACE
class QGraphicsItem;
QT_END_NAMESPACE
......@@ -99,6 +101,7 @@ public:
protected:
virtual void selectedItemsChanged(const QList<FormEditorItem*> &itemList) = 0;
virtual void showContextMenu(QGraphicsSceneMouseEvent *event);
Snapper::Snapping generateUseSnapping(Qt::KeyboardModifiers keyboardModifier) const;
FormEditorView *view() const;
void setView(FormEditorView *view);
......
......@@ -275,8 +275,8 @@ void DragTool::dropEvent(QGraphicsSceneDragDropEvent * event)
if (event->mimeData()->hasFormat("application/vnd.bauhaus.itemlibraryinfo") ||
event->mimeData()->hasFormat("application/vnd.bauhaus.libraryresource")) {
event->accept();
end(event->scenePos());
//Q_ASSERT(m_token.isValid());
end(generateUseSnapping(event->modifiers()));
try {
m_rewriterTransaction.commit();
} catch (RewritingException &e) {
......@@ -333,7 +333,9 @@ void DragTool::dragLeaveEvent(QGraphicsSceneDragDropEvent * event)
event->accept();
if (m_dragNode.isValid())
m_dragNode.destroy();
end(event->scenePos());
m_moveManipulator.end();
clear();
try {
m_rewriterTransaction.commit();
......@@ -367,7 +369,7 @@ void DragTool::dragMoveEvent(QGraphicsSceneDragDropEvent * event)
FormEditorItem *parentItem = calculateContainer(event->scenePos() + QPoint(2, 2));
if (!parentItem) { //if there is no parent any more - the use left the scene
end(event->scenePos());
end();
QmlDesignerItemLibraryDragAndDrop::CustomDragAndDrop::show();
m_dragNode.destroy(); //delete the node then
return;
......@@ -405,9 +407,15 @@ void DragTool::dragMoveEvent(QGraphicsSceneDragDropEvent * event)
}
}
void DragTool::end(QPointF scenePos)
void DragTool::end()
{
m_moveManipulator.end();
clear();
}
void DragTool::end(Snapper::Snapping useSnapping)
{
m_moveManipulator.end(scenePos);
m_moveManipulator.end(useSnapping);
clear();
}
......@@ -424,7 +432,7 @@ void DragTool::move(QPointF scenePos)
}
//MoveManipulator::Snapping useSnapping = MoveManipulator::NoSnapping;
MoveManipulator::Snapping useSnapping = MoveManipulator::UseSnapping;
Snapper::Snapping useSnapping = Snapper::UseSnapping;
/* if (event->modifiers().testFlag(Qt::ControlModifier) != view()->isSnapButtonChecked())
useSnapping = MoveManipulator::UseSnapping;*/
......
......@@ -116,7 +116,8 @@ private:
QList<Import> missingImportList(const ItemLibraryEntry &itemLibraryEntry);
void begin(QPointF scenePos);
void end(QPointF scenePos);
void end();
void end(Snapper::Snapping useSnapping);
void move(QPointF scenePos);
MoveManipulator m_moveManipulator;
......
......@@ -60,17 +60,23 @@ FormEditorWidget::FormEditorWidget(FormEditorView *view)
m_toolActionGroup = new QActionGroup(this);
m_transformToolAction = m_toolActionGroup->addAction(tr("Transform Tool (Q)."));
m_transformToolAction->setShortcut(Qt::Key_Q);
m_transformToolAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
m_transformToolAction->setCheckable(true);
m_transformToolAction->setChecked(true);
m_transformToolAction->setIcon(QPixmap(":/icon/tool/transform.png"));
connect(m_transformToolAction.data(), SIGNAL(triggered(bool)), SLOT(changeTransformTool(bool)));
QActionGroup *layoutActionGroup = new QActionGroup(this);
layoutActionGroup->setExclusive(true);
m_noSnappingAction = layoutActionGroup->addAction(tr("No snapping (T)."));
m_noSnappingAction->setShortcut(Qt::Key_W);
m_noSnappingAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
m_noSnappingAction->setCheckable(true);
m_noSnappingAction->setChecked(true);
m_noSnappingAction->setIcon(QPixmap(":/icon/layout/no_snapping.png"));
m_snappingAndAnchoringAction = layoutActionGroup->addAction(tr("Snapping with anchoring (W)."));
m_snappingAndAnchoringAction->setShortcut(Qt::Key_W);
m_snappingAndAnchoringAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
m_snappingAndAnchoringAction->setCheckable(true);
m_snappingAndAnchoringAction->setChecked(true);
m_snappingAndAnchoringAction->setIcon(QPixmap(":/icon/layout/snapping_and_anchoring.png"));
QActionGroup *layoutActionGroup = new QActionGroup(this);
layoutActionGroup->setExclusive(false);
m_snappingAction = layoutActionGroup->addAction(tr("Snap to guides (E)."));
m_snappingAction->setShortcut(Qt::Key_E);
m_snappingAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
......@@ -78,14 +84,6 @@ FormEditorWidget::FormEditorWidget(FormEditorView *view)
m_snappingAction->setChecked(true);
m_snappingAction->setIcon(QPixmap(":/icon/layout/snapping.png"));
m_snappingAndAnchoringAction = layoutActionGroup->addAction(tr("Toggle snapping and anchoring (R)."));
m_snappingAndAnchoringAction->setShortcut(Qt::Key_R);
m_snappingAndAnchoringAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
m_snappingAndAnchoringAction->setCheckable(true);
m_snappingAndAnchoringAction->setChecked(false);
m_snappingAndAnchoringAction->setEnabled(false);
m_snappingAndAnchoringAction->setVisible(false);
m_snappingAndAnchoringAction->setIcon(QPixmap(":/icon/layout/snapping_and_anchoring.png"));
addActions(layoutActionGroup->actions());
upperActions.append(layoutActionGroup->actions());
......@@ -125,8 +123,6 @@ FormEditorWidget::FormEditorWidget(FormEditorView *view)
addAction(m_rootHeightAction.data());
upperActions.append(m_rootHeightAction.data());
m_snappingAndAnchoringAction = layoutActionGroup->addAction(tr("Toggle snapping and anchoring (R)."));
m_toolBox = new ToolBox(this);
fillLayout->addWidget(m_toolBox.data());
m_toolBox->setLeftSideActions(upperActions);
......@@ -246,11 +242,6 @@ ZoomAction *FormEditorWidget::zoomAction() const
return m_zoomAction.data();
}
QAction *FormEditorWidget::transformToolAction() const
{
return m_transformToolAction.data();
}
QAction *FormEditorWidget::showBoundingRectAction() const
{
return m_showBoundingRectAction.data();
......
......@@ -56,7 +56,6 @@ public:
FormEditorWidget(FormEditorView *view);
ZoomAction *zoomAction() const;
QAction *transformToolAction() const;
QAction *showBoundingRectAction() const;
QAction *selectOnlyContentItemsAction() const;
QAction *snappingAction() const;
......
......@@ -102,7 +102,7 @@ void MoveManipulator::synchronizeParent(const QList<FormEditorItem*> &itemList,
}
if (!parentNode.metaInfo().isSubclassOf("<cpp>.QDeclarativeBasePositioner", -1, -1))
update(m_lastPosition, NoSnapping, UseBaseState);
update(m_lastPosition, Snapper::NoSnapping, UseBaseState);
}
void MoveManipulator::synchronizeInstanceParent(const QList<FormEditorItem*> &itemList)
......@@ -260,7 +260,7 @@ QHash<FormEditorItem*, QRectF> MoveManipulator::tanslatedBoundingRects(const QHa
/*
/brief updates the position of the items.
*/
void MoveManipulator::update(const QPointF& updatePoint, Snapping useSnapping, State stateToBeManipulated)
void MoveManipulator::update(const QPointF& updatePoint, Snapper::Snapping useSnapping, State stateToBeManipulated)
{
m_lastPosition = updatePoint;
deleteSnapLines(); //Since they position is changed and the item is moved the snapping lines are
......@@ -275,12 +275,7 @@ void MoveManipulator::update(const QPointF& updatePoint, Snapping useSnapping, S
QPointF offsetVector(updatePointInContainerSpace - beginPointInContainerSpace);
if (useSnapping == UseSnappingAndAnchoring)
{
}
if (useSnapping == UseSnapping || useSnapping == UseSnappingAndAnchoring) {
if (useSnapping == Snapper::UseSnapping || useSnapping == Snapper::UseSnappingAndAnchoring) {
offsetVector -= findSnappingOffset(tanslatedBoundingRects(m_beginItemRectHash, offsetVector));
generateSnappingLines(tanslatedBoundingRects(m_beginItemRectHash, offsetVector));
}
......@@ -386,15 +381,25 @@ void MoveManipulator::reparentTo(FormEditorItem *newParent)
}
}
void MoveManipulator::end(const QPointF &/*endPoint*/)
void MoveManipulator::end()
{
m_isActive = false;
deleteSnapLines();
// setOpacityForAllElements(1.0);
clear();
}
void MoveManipulator::end(Snapper::Snapping useSnapping)
{
if (useSnapping == Snapper::UseSnappingAndAnchoring) {
foreach (FormEditorItem *formEditorItem, m_itemList)
m_snapper.adjustAnchoringOfItem(formEditorItem);
}
end();
}
void MoveManipulator::moveBy(double deltaX, double deltaY)
{
foreach (FormEditorItem* item, m_itemList) {
......
......@@ -49,12 +49,6 @@ class Model;
class MoveManipulator
{
public:
enum Snapping {
UseSnapping,
UseSnappingAndAnchoring,
NoSnapping
};
enum State {
UseActualState,
UseBaseState
......@@ -68,9 +62,10 @@ public:
void synchronizeParent(const QList<FormEditorItem*> &itemList, const ModelNode &parentNode);
void begin(const QPointF& beginPoint);
void update(const QPointF& updatePoint, Snapping useSnapping, State stateToBeManipulated = UseActualState);
void update(const QPointF& updatePoint, Snapper::Snapping useSnapping, State stateToBeManipulated = UseActualState);
void reparentTo(FormEditorItem *newParent);
void end(const QPointF& endPoint);
void end();
void end(Snapper::Snapping useSnapping);
void moveBy(double deltaX, double deltaY);
......@@ -101,6 +96,8 @@ protected:
void setPosition(QmlItemNode itemNode, const QPointF &position);
void adjustAnchoringOfItem(FormEditorItem *item);
private:
Snapper m_snapper;
QWeakPointer<LayerItem> m_layerItem;
......
......@@ -103,18 +103,7 @@ void MoveTool::mouseMoveEvent(const QList<QGraphicsItem*> &itemList,
}
}
bool shouldSnapping = view()->formEditorWidget()->snappingAction()->isChecked();
bool shouldSnappingAndAnchoring = view()->formEditorWidget()->snappingAndAnchoringAction()->isChecked();
MoveManipulator::Snapping useSnapping = MoveManipulator::NoSnapping;
if (event->modifiers().testFlag(Qt::ControlModifier) != (shouldSnapping || shouldSnappingAndAnchoring)) {
if (shouldSnappingAndAnchoring)
useSnapping = MoveManipulator::UseSnappingAndAnchoring;
else
useSnapping = MoveManipulator::UseSnapping;
}
m_moveManipulator.update(event->scenePos(), useSnapping);
m_moveManipulator.update(event->scenePos(), generateUseSnapping(event->modifiers()));
}
}
......@@ -214,25 +203,11 @@ void MoveTool::mouseReleaseEvent(const QList<QGraphicsItem*> &itemList,
if (m_movingItems.isEmpty())
return;
QLineF moveVector(event->scenePos(), m_moveManipulator.beginPoint());
if (moveVector.length() < QApplication::startDragDistance())
{
QPointF beginPoint(m_moveManipulator.beginPoint());
m_moveManipulator.end(beginPoint);
// m_selectionIndicator.show();
m_resizeIndicator.show();
m_movingItems.clear();
m_moveManipulator.end(generateUseSnapping(event->modifiers()));
view()->changeToSelectionTool(event);
} else {
m_moveManipulator.end(event->scenePos());
m_selectionIndicator.show();
m_resizeIndicator.show();
m_movingItems.clear();
}
m_selectionIndicator.show();
m_resizeIndicator.show();
m_movingItems.clear();
}
AbstractFormEditorTool::mouseReleaseEvent(itemList, event);
......
......@@ -99,14 +99,14 @@ void ResizeManipulator::begin(const QPointF &/*beginPoint*/)
// return QSizeF(sizeAsPoint.x(), sizeAsPoint.y());
//}
void ResizeManipulator::update(const QPointF& updatePoint, Snapping useSnapping)
void ResizeManipulator::update(const QPointF& updatePoint, Snapper::Snapping useSnapping)
{
const double minimumWidth = 0.0;
const double minimumHeight = 0.0;
deleteSnapLines();
bool snap = useSnapping == UseSnapping || useSnapping == UseSnappingAndAnchoring;
bool snap = useSnapping == Snapper::UseSnapping || useSnapping == Snapper::UseSnappingAndAnchoring;
if (m_resizeController.isValid()) {
......@@ -376,8 +376,15 @@ void ResizeManipulator::update(const QPointF& updatePoint, Snapping useSnapping)
}
}
void ResizeManipulator::end()
void ResizeManipulator::end(Snapper::Snapping useSnapping)
{
if (useSnapping == Snapper::UseSnappingAndAnchoring) {
deleteSnapLines();
m_snapper.setTransformtionSpaceFormEditorItem(m_snapper.containerFormEditorItem());
m_snapper.updateSnappingLines(m_resizeController.formEditorItem());
m_snapper.adjustAnchoringOfItem(m_resizeController.formEditorItem());
}
m_isActive = false;
m_rewriterTransaction.commit();
clear();
......
......@@ -44,12 +44,6 @@ class Model;
class ResizeManipulator
{
public:
enum Snapping {
UseSnapping,
UseSnappingAndAnchoring,
NoSnapping
};
ResizeManipulator(LayerItem *layerItem, FormEditorView *view);
~ResizeManipulator();
......@@ -57,8 +51,8 @@ public:
void removeHandle();
void begin(const QPointF& beginPoint);
void update(const QPointF& updatePoint, Snapping useSnapping);
void end();
void update(const QPointF& updatePoint, Snapper::Snapping useSnapping);
void end(Snapper::Snapping useSnapping);
void moveBy(double deltaX, double deltaY);
......
......@@ -75,20 +75,8 @@ void ResizeTool::mousePressEvent(const QList<QGraphicsItem*> &itemList,
void ResizeTool::mouseMoveEvent(const QList<QGraphicsItem*> &,
QGraphicsSceneMouseEvent *event)
{
if (m_resizeManipulator.isActive()) {
bool shouldSnapping = view()->formEditorWidget()->snappingAction()->isChecked();
bool shouldSnappingAndAnchoring = view()->formEditorWidget()->snappingAndAnchoringAction()->isChecked();
ResizeManipulator::Snapping useSnapping = ResizeManipulator::NoSnapping;
if (event->modifiers().testFlag(Qt::ControlModifier) != (shouldSnapping || shouldSnappingAndAnchoring)) {
if (shouldSnappingAndAnchoring)
useSnapping = ResizeManipulator::UseSnappingAndAnchoring;
else
useSnapping = ResizeManipulator::UseSnapping;
}
m_resizeManipulator.update(event->scenePos(), useSnapping);
}
if (m_resizeManipulator.isActive())
m_resizeManipulator.update(event->scenePos(), generateUseSnapping(event->modifiers()));
}
void ResizeTool::hoverMoveEvent(const QList<QGraphicsItem*> &itemList,
......@@ -128,7 +116,7 @@ void ResizeTool::mouseReleaseEvent(const QList<QGraphicsItem*> &itemList,
m_selectionIndicator.show();
m_resizeIndicator.show();
m_resizeManipulator.end();
m_resizeManipulator.end(generateUseSnapping(event->modifiers()));
}
AbstractFormEditorTool::mouseReleaseEvent(itemList, event);
......
......@@ -35,6 +35,7 @@
#include <QLineF>
#include <QPen>
#include <QApplication>
#include <qmlanchors.h>
namespace QmlDesigner {
......@@ -525,11 +526,6 @@ static QList<QLineF> mergedHorizontalLines(const QList<QLineF> &lineList)
return mergedLineList;
}
static QList<QLineF> mergedVerticalLines(const QList<QLineF> &lineList)
{
QList<QLineF> mergedLineList;
......@@ -567,6 +563,155 @@ QList<QGraphicsItem*> Snapper::generateSnappingLines(const QRectF &boundingRect,
return generateSnappingLines(boundingRectList, layerItem, transform);
}
static QmlItemNode findItemOnSnappingLine(const QmlItemNode &sourceQmlItemNode, const SnapLineMap &snappingLines, double anchorLine, AnchorLine::Type anchorLineType)
{
QmlItemNode targetQmlItemNode;
double targetAnchorLine = 0.0;
targetAnchorLine = std::numeric_limits<double>::max();
AnchorLine::Type compareAnchorLineType;
if (anchorLineType == AnchorLine::Left
|| anchorLineType == AnchorLine::Right)
compareAnchorLineType = AnchorLine::Top;
else
compareAnchorLineType = AnchorLine::Left;
SnapLineMapIterator snapLineIterator(snappingLines);
while (snapLineIterator.hasNext()) {
snapLineIterator.next();
double snapLine = snapLineIterator.key();
if (qAbs(snapLine - anchorLine ) < 1.0) {
QmlItemNode possibleAchorItemNode = snapLineIterator.value().second->qmlItemNode();
double currentToAnchorLine = possibleAchorItemNode.anchors().instanceAnchorLine(compareAnchorLineType);
if (possibleAchorItemNode != sourceQmlItemNode) {
if (sourceQmlItemNode.instanceParent() == possibleAchorItemNode) {
targetQmlItemNode = possibleAchorItemNode;
targetAnchorLine = currentToAnchorLine;
break;
} else if (currentToAnchorLine < targetAnchorLine) {
targetQmlItemNode = possibleAchorItemNode;
targetAnchorLine = currentToAnchorLine;
}
}
}
}
return targetQmlItemNode;
}
static void adjustAnchorLine(const QmlItemNode &sourceQmlItemNode,
const QmlItemNode &containerQmlItemNode,
const SnapLineMap &snappingLines,
const SnapLineMap &snappingOffsets,
AnchorLine::Type lineAnchorLineType,
AnchorLine::Type offsetAnchorLineType)
{
QmlAnchors qmlAnchors = sourceQmlItemNode.anchors();
double fromAnchorLine = sourceQmlItemNode.anchors().instanceAnchorLine(lineAnchorLineType);
QmlItemNode targetQmlItemNode = findItemOnSnappingLine(sourceQmlItemNode, snappingLines, fromAnchorLine, lineAnchorLineType);
if (targetQmlItemNode.isValid() && !targetQmlItemNode.anchors().checkForCycle(lineAnchorLineType, sourceQmlItemNode)) {
double margin = 0.0;
if (targetQmlItemNode == containerQmlItemNode) {
if (lineAnchorLineType == AnchorLine::Left
|| lineAnchorLineType == AnchorLine::Top)
margin = fromAnchorLine;
else if (lineAnchorLineType == AnchorLine::Right)
margin = targetQmlItemNode.instanceSize().width() - fromAnchorLine;
else if (lineAnchorLineType == AnchorLine::Bottom)
margin = targetQmlItemNode.instanceSize().height() - fromAnchorLine;
}
if (!qFuzzyIsNull(margin) || !qFuzzyIsNull(qmlAnchors.instanceMargin(lineAnchorLineType)))
qmlAnchors.setMargin(lineAnchorLineType, margin);
qmlAnchors.setAnchor(lineAnchorLineType, targetQmlItemNode, lineAnchorLineType);
} else if (!snappingOffsets.isEmpty()) {
targetQmlItemNode = findItemOnSnappingLine(sourceQmlItemNode, snappingOffsets, fromAnchorLine, lineAnchorLineType);
if (targetQmlItemNode.isValid() && !targetQmlItemNode.anchors().checkForCycle(lineAnchorLineType, sourceQmlItemNode)) {
double margin = fromAnchorLine - targetQmlItemNode.anchors().instanceAnchorLine(offsetAnchorLineType);
if (lineAnchorLineType == AnchorLine::Right
|| lineAnchorLineType == AnchorLine::Bottom)
margin *= -1.;
if (!qFuzzyIsNull(margin) || !qFuzzyIsNull(qmlAnchors.instanceMargin(lineAnchorLineType)))
qmlAnchors.setMargin(lineAnchorLineType, margin);
qmlAnchors.setAnchor(lineAnchorLineType, targetQmlItemNode, offsetAnchorLineType);
}
}
}
void Snapper::adjustAnchoringOfItem(FormEditorItem *formEditorItem)
{
QmlItemNode qmlItemNode = formEditorItem->qmlItemNode();
QmlAnchors qmlAnchors = qmlItemNode.anchors();
if (!qmlAnchors.instanceHasAnchor(AnchorLine::HorizontalCenter)) {
adjustAnchorLine(qmlItemNode,
containerFormEditorItem()->qmlItemNode(),
containerFormEditorItem()->leftSnappingLines(),
containerFormEditorItem()->rightSnappingOffsets(),
AnchorLine::Left,
AnchorLine::Right);
}
if (!qmlAnchors.instanceHasAnchor(AnchorLine::VerticalCenter)) {
adjustAnchorLine(qmlItemNode,
containerFormEditorItem()->qmlItemNode(),
containerFormEditorItem()->topSnappingLines(),
containerFormEditorItem()->bottomSnappingOffsets(),
AnchorLine::Top,
AnchorLine::Bottom);
}
if (!qmlAnchors.instanceHasAnchor(AnchorLine::VerticalCenter)) {
adjustAnchorLine(qmlItemNode,
containerFormEditorItem()->qmlItemNode(),
containerFormEditorItem()->bottomSnappingLines(),
containerFormEditorItem()->topSnappingOffsets(),
AnchorLine::Bottom,
AnchorLine::Top);
}
if (!qmlAnchors.instanceHasAnchor(AnchorLine::HorizontalCenter)) {
adjustAnchorLine(qmlItemNode,