QmlDesigner: Support anchoring at snapping

Change-Id: I3ec504e931ee63761538acb4666a3c8ce1a592e5
Reviewed-by: Thomas Hartmann <Thomas.Hartmann@digia.com>
This commit is contained in:
Marco Bubke
2013-05-14 16:21:29 +02:00
parent 81cbe76c85
commit fc6d51aff1
16 changed files with 387 additions and 116 deletions

View File

@@ -29,6 +29,7 @@
#include "abstractformeditortool.h" #include "abstractformeditortool.h"
#include "formeditorview.h" #include "formeditorview.h"
#include "formeditorwidget.h"
#include <modelnodecontextmenu.h> #include <modelnodecontextmenu.h>
@@ -36,6 +37,7 @@
#include <QGraphicsSceneDragDropEvent> #include <QGraphicsSceneDragDropEvent>
#include <QMimeData> #include <QMimeData>
#include <nodemetainfo.h> #include <nodemetainfo.h>
#include <QAction>
namespace QmlDesigner { namespace QmlDesigner {
@@ -219,6 +221,22 @@ void AbstractFormEditorTool::showContextMenu(QGraphicsSceneMouseEvent *event)
ModelNodeContextMenu::showContextMenu(view(), event->screenPos(), event->scenePos().toPoint(), true); 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() void AbstractFormEditorTool::clear()
{ {
m_itemList.clear(); m_itemList.clear();

View File

@@ -34,6 +34,8 @@
#include <qmldesignercorelib_global.h> #include <qmldesignercorelib_global.h>
#include "snapper.h"
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
class QGraphicsItem; class QGraphicsItem;
QT_END_NAMESPACE QT_END_NAMESPACE
@@ -99,6 +101,7 @@ public:
protected: protected:
virtual void selectedItemsChanged(const QList<FormEditorItem*> &itemList) = 0; virtual void selectedItemsChanged(const QList<FormEditorItem*> &itemList) = 0;
virtual void showContextMenu(QGraphicsSceneMouseEvent *event); virtual void showContextMenu(QGraphicsSceneMouseEvent *event);
Snapper::Snapping generateUseSnapping(Qt::KeyboardModifiers keyboardModifier) const;
FormEditorView *view() const; FormEditorView *view() const;
void setView(FormEditorView *view); void setView(FormEditorView *view);

View File

@@ -275,8 +275,8 @@ void DragTool::dropEvent(QGraphicsSceneDragDropEvent * event)
if (event->mimeData()->hasFormat("application/vnd.bauhaus.itemlibraryinfo") || if (event->mimeData()->hasFormat("application/vnd.bauhaus.itemlibraryinfo") ||
event->mimeData()->hasFormat("application/vnd.bauhaus.libraryresource")) { event->mimeData()->hasFormat("application/vnd.bauhaus.libraryresource")) {
event->accept(); event->accept();
end(event->scenePos()); end(generateUseSnapping(event->modifiers()));
//Q_ASSERT(m_token.isValid());
try { try {
m_rewriterTransaction.commit(); m_rewriterTransaction.commit();
} catch (RewritingException &e) { } catch (RewritingException &e) {
@@ -333,7 +333,9 @@ void DragTool::dragLeaveEvent(QGraphicsSceneDragDropEvent * event)
event->accept(); event->accept();
if (m_dragNode.isValid()) if (m_dragNode.isValid())
m_dragNode.destroy(); m_dragNode.destroy();
end(event->scenePos());
m_moveManipulator.end();
clear();
try { try {
m_rewriterTransaction.commit(); m_rewriterTransaction.commit();
@@ -367,7 +369,7 @@ void DragTool::dragMoveEvent(QGraphicsSceneDragDropEvent * event)
FormEditorItem *parentItem = calculateContainer(event->scenePos() + QPoint(2, 2)); FormEditorItem *parentItem = calculateContainer(event->scenePos() + QPoint(2, 2));
if (!parentItem) { //if there is no parent any more - the use left the scene if (!parentItem) { //if there is no parent any more - the use left the scene
end(event->scenePos()); end();
QmlDesignerItemLibraryDragAndDrop::CustomDragAndDrop::show(); QmlDesignerItemLibraryDragAndDrop::CustomDragAndDrop::show();
m_dragNode.destroy(); //delete the node then m_dragNode.destroy(); //delete the node then
return; return;
@@ -405,9 +407,15 @@ void DragTool::dragMoveEvent(QGraphicsSceneDragDropEvent * event)
} }
} }
void DragTool::end(QPointF scenePos) void DragTool::end()
{ {
m_moveManipulator.end(scenePos); m_moveManipulator.end();
clear();
}
void DragTool::end(Snapper::Snapping useSnapping)
{
m_moveManipulator.end(useSnapping);
clear(); clear();
} }
@@ -424,7 +432,7 @@ void DragTool::move(QPointF scenePos)
} }
//MoveManipulator::Snapping useSnapping = MoveManipulator::NoSnapping; //MoveManipulator::Snapping useSnapping = MoveManipulator::NoSnapping;
MoveManipulator::Snapping useSnapping = MoveManipulator::UseSnapping; Snapper::Snapping useSnapping = Snapper::UseSnapping;
/* if (event->modifiers().testFlag(Qt::ControlModifier) != view()->isSnapButtonChecked()) /* if (event->modifiers().testFlag(Qt::ControlModifier) != view()->isSnapButtonChecked())
useSnapping = MoveManipulator::UseSnapping;*/ useSnapping = MoveManipulator::UseSnapping;*/

View File

@@ -116,7 +116,8 @@ private:
QList<Import> missingImportList(const ItemLibraryEntry &itemLibraryEntry); QList<Import> missingImportList(const ItemLibraryEntry &itemLibraryEntry);
void begin(QPointF scenePos); void begin(QPointF scenePos);
void end(QPointF scenePos); void end();
void end(Snapper::Snapping useSnapping);
void move(QPointF scenePos); void move(QPointF scenePos);
MoveManipulator m_moveManipulator; MoveManipulator m_moveManipulator;

View File

@@ -60,17 +60,23 @@ FormEditorWidget::FormEditorWidget(FormEditorView *view)
m_toolActionGroup = new QActionGroup(this); 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); QActionGroup *layoutActionGroup = new QActionGroup(this);
layoutActionGroup->setExclusive(false); 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"));
m_snappingAction = layoutActionGroup->addAction(tr("Snap to guides (E).")); m_snappingAction = layoutActionGroup->addAction(tr("Snap to guides (E)."));
m_snappingAction->setShortcut(Qt::Key_E); m_snappingAction->setShortcut(Qt::Key_E);
m_snappingAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); m_snappingAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
@@ -78,14 +84,6 @@ FormEditorWidget::FormEditorWidget(FormEditorView *view)
m_snappingAction->setChecked(true); m_snappingAction->setChecked(true);
m_snappingAction->setIcon(QPixmap(":/icon/layout/snapping.png")); 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()); addActions(layoutActionGroup->actions());
upperActions.append(layoutActionGroup->actions()); upperActions.append(layoutActionGroup->actions());
@@ -125,8 +123,6 @@ FormEditorWidget::FormEditorWidget(FormEditorView *view)
addAction(m_rootHeightAction.data()); addAction(m_rootHeightAction.data());
upperActions.append(m_rootHeightAction.data()); upperActions.append(m_rootHeightAction.data());
m_snappingAndAnchoringAction = layoutActionGroup->addAction(tr("Toggle snapping and anchoring (R)."));
m_toolBox = new ToolBox(this); m_toolBox = new ToolBox(this);
fillLayout->addWidget(m_toolBox.data()); fillLayout->addWidget(m_toolBox.data());
m_toolBox->setLeftSideActions(upperActions); m_toolBox->setLeftSideActions(upperActions);
@@ -246,11 +242,6 @@ ZoomAction *FormEditorWidget::zoomAction() const
return m_zoomAction.data(); return m_zoomAction.data();
} }
QAction *FormEditorWidget::transformToolAction() const
{
return m_transformToolAction.data();
}
QAction *FormEditorWidget::showBoundingRectAction() const QAction *FormEditorWidget::showBoundingRectAction() const
{ {
return m_showBoundingRectAction.data(); return m_showBoundingRectAction.data();

View File

@@ -56,7 +56,6 @@ public:
FormEditorWidget(FormEditorView *view); FormEditorWidget(FormEditorView *view);
ZoomAction *zoomAction() const; ZoomAction *zoomAction() const;
QAction *transformToolAction() const;
QAction *showBoundingRectAction() const; QAction *showBoundingRectAction() const;
QAction *selectOnlyContentItemsAction() const; QAction *selectOnlyContentItemsAction() const;
QAction *snappingAction() const; QAction *snappingAction() const;

View File

@@ -102,7 +102,7 @@ void MoveManipulator::synchronizeParent(const QList<FormEditorItem*> &itemList,
} }
if (!parentNode.metaInfo().isSubclassOf("<cpp>.QDeclarativeBasePositioner", -1, -1)) 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) 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. /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; m_lastPosition = updatePoint;
deleteSnapLines(); //Since they position is changed and the item is moved the snapping lines are 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); QPointF offsetVector(updatePointInContainerSpace - beginPointInContainerSpace);
if (useSnapping == UseSnappingAndAnchoring) if (useSnapping == Snapper::UseSnapping || useSnapping == Snapper::UseSnappingAndAnchoring) {
{
}
if (useSnapping == UseSnapping || useSnapping == UseSnappingAndAnchoring) {
offsetVector -= findSnappingOffset(tanslatedBoundingRects(m_beginItemRectHash, offsetVector)); offsetVector -= findSnappingOffset(tanslatedBoundingRects(m_beginItemRectHash, offsetVector));
generateSnappingLines(tanslatedBoundingRects(m_beginItemRectHash, offsetVector)); generateSnappingLines(tanslatedBoundingRects(m_beginItemRectHash, offsetVector));
} }
@@ -386,15 +381,25 @@ void MoveManipulator::reparentTo(FormEditorItem *newParent)
} }
} }
void MoveManipulator::end()
void MoveManipulator::end(const QPointF &/*endPoint*/)
{ {
m_isActive = false; m_isActive = false;
deleteSnapLines(); deleteSnapLines();
// setOpacityForAllElements(1.0);
clear(); 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) void MoveManipulator::moveBy(double deltaX, double deltaY)
{ {
foreach (FormEditorItem* item, m_itemList) { foreach (FormEditorItem* item, m_itemList) {

View File

@@ -49,12 +49,6 @@ class Model;
class MoveManipulator class MoveManipulator
{ {
public: public:
enum Snapping {
UseSnapping,
UseSnappingAndAnchoring,
NoSnapping
};
enum State { enum State {
UseActualState, UseActualState,
UseBaseState UseBaseState
@@ -68,9 +62,10 @@ public:
void synchronizeParent(const QList<FormEditorItem*> &itemList, const ModelNode &parentNode); void synchronizeParent(const QList<FormEditorItem*> &itemList, const ModelNode &parentNode);
void begin(const QPointF& beginPoint); 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 reparentTo(FormEditorItem *newParent);
void end(const QPointF& endPoint); void end();
void end(Snapper::Snapping useSnapping);
void moveBy(double deltaX, double deltaY); void moveBy(double deltaX, double deltaY);
@@ -101,6 +96,8 @@ protected:
void setPosition(QmlItemNode itemNode, const QPointF &position); void setPosition(QmlItemNode itemNode, const QPointF &position);
void adjustAnchoringOfItem(FormEditorItem *item);
private: private:
Snapper m_snapper; Snapper m_snapper;
QWeakPointer<LayerItem> m_layerItem; QWeakPointer<LayerItem> m_layerItem;

View File

@@ -103,18 +103,7 @@ void MoveTool::mouseMoveEvent(const QList<QGraphicsItem*> &itemList,
} }
} }
bool shouldSnapping = view()->formEditorWidget()->snappingAction()->isChecked(); m_moveManipulator.update(event->scenePos(), generateUseSnapping(event->modifiers()));
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);
} }
} }
@@ -214,25 +203,11 @@ void MoveTool::mouseReleaseEvent(const QList<QGraphicsItem*> &itemList,
if (m_movingItems.isEmpty()) if (m_movingItems.isEmpty())
return; return;
QLineF moveVector(event->scenePos(), m_moveManipulator.beginPoint()); m_moveManipulator.end(generateUseSnapping(event->modifiers()));
if (moveVector.length() < QApplication::startDragDistance())
{
QPointF beginPoint(m_moveManipulator.beginPoint());
m_moveManipulator.end(beginPoint); m_selectionIndicator.show();
m_resizeIndicator.show();
// m_selectionIndicator.show(); m_movingItems.clear();
m_resizeIndicator.show();
m_movingItems.clear();
view()->changeToSelectionTool(event);
} else {
m_moveManipulator.end(event->scenePos());
m_selectionIndicator.show();
m_resizeIndicator.show();
m_movingItems.clear();
}
} }
AbstractFormEditorTool::mouseReleaseEvent(itemList, event); AbstractFormEditorTool::mouseReleaseEvent(itemList, event);

View File

@@ -99,14 +99,14 @@ void ResizeManipulator::begin(const QPointF &/*beginPoint*/)
// return QSizeF(sizeAsPoint.x(), sizeAsPoint.y()); // 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 minimumWidth = 0.0;
const double minimumHeight = 0.0; const double minimumHeight = 0.0;
deleteSnapLines(); deleteSnapLines();
bool snap = useSnapping == UseSnapping || useSnapping == UseSnappingAndAnchoring; bool snap = useSnapping == Snapper::UseSnapping || useSnapping == Snapper::UseSnappingAndAnchoring;
if (m_resizeController.isValid()) { 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_isActive = false;
m_rewriterTransaction.commit(); m_rewriterTransaction.commit();
clear(); clear();

View File

@@ -44,12 +44,6 @@ class Model;
class ResizeManipulator class ResizeManipulator
{ {
public: public:
enum Snapping {
UseSnapping,
UseSnappingAndAnchoring,
NoSnapping
};
ResizeManipulator(LayerItem *layerItem, FormEditorView *view); ResizeManipulator(LayerItem *layerItem, FormEditorView *view);
~ResizeManipulator(); ~ResizeManipulator();
@@ -57,8 +51,8 @@ public:
void removeHandle(); void removeHandle();
void begin(const QPointF& beginPoint); void begin(const QPointF& beginPoint);
void update(const QPointF& updatePoint, Snapping useSnapping); void update(const QPointF& updatePoint, Snapper::Snapping useSnapping);
void end(); void end(Snapper::Snapping useSnapping);
void moveBy(double deltaX, double deltaY); void moveBy(double deltaX, double deltaY);

View File

@@ -75,20 +75,8 @@ void ResizeTool::mousePressEvent(const QList<QGraphicsItem*> &itemList,
void ResizeTool::mouseMoveEvent(const QList<QGraphicsItem*> &, void ResizeTool::mouseMoveEvent(const QList<QGraphicsItem*> &,
QGraphicsSceneMouseEvent *event) QGraphicsSceneMouseEvent *event)
{ {
if (m_resizeManipulator.isActive()) { if (m_resizeManipulator.isActive())
bool shouldSnapping = view()->formEditorWidget()->snappingAction()->isChecked(); m_resizeManipulator.update(event->scenePos(), generateUseSnapping(event->modifiers()));
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);
}
} }
void ResizeTool::hoverMoveEvent(const QList<QGraphicsItem*> &itemList, void ResizeTool::hoverMoveEvent(const QList<QGraphicsItem*> &itemList,
@@ -128,7 +116,7 @@ void ResizeTool::mouseReleaseEvent(const QList<QGraphicsItem*> &itemList,
m_selectionIndicator.show(); m_selectionIndicator.show();
m_resizeIndicator.show(); m_resizeIndicator.show();
m_resizeManipulator.end(); m_resizeManipulator.end(generateUseSnapping(event->modifiers()));
} }
AbstractFormEditorTool::mouseReleaseEvent(itemList, event); AbstractFormEditorTool::mouseReleaseEvent(itemList, event);

View File

@@ -35,6 +35,7 @@
#include <QLineF> #include <QLineF>
#include <QPen> #include <QPen>
#include <QApplication> #include <QApplication>
#include <qmlanchors.h>
namespace QmlDesigner { namespace QmlDesigner {
@@ -525,11 +526,6 @@ static QList<QLineF> mergedHorizontalLines(const QList<QLineF> &lineList)
return mergedLineList; return mergedLineList;
} }
static QList<QLineF> mergedVerticalLines(const QList<QLineF> &lineList) static QList<QLineF> mergedVerticalLines(const QList<QLineF> &lineList)
{ {
QList<QLineF> mergedLineList; QList<QLineF> mergedLineList;
@@ -567,6 +563,155 @@ QList<QGraphicsItem*> Snapper::generateSnappingLines(const QRectF &boundingRect,
return generateSnappingLines(boundingRectList, layerItem, transform); 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,
containerFormEditorItem()->qmlItemNode(),
containerFormEditorItem()->rightSnappingLines(),
containerFormEditorItem()->leftSnappingOffsets(),
AnchorLine::Right,
AnchorLine::Left);
}
if (!qmlAnchors.instanceHasAnchor(AnchorLine::Left) && !qmlAnchors.instanceHasAnchor(AnchorLine::Right)) {
adjustAnchorLine(qmlItemNode,
containerFormEditorItem()->qmlItemNode(),
containerFormEditorItem()->verticalCenterSnappingLines(),
SnapLineMap(),
AnchorLine::HorizontalCenter,
AnchorLine::HorizontalCenter);
}
if (!qmlAnchors.instanceHasAnchor(AnchorLine::Top) && !qmlAnchors.instanceHasAnchor(AnchorLine::Bottom)) {
adjustAnchorLine(qmlItemNode,
containerFormEditorItem()->qmlItemNode(),
containerFormEditorItem()->horizontalCenterSnappingLines(),
SnapLineMap(),
AnchorLine::VerticalCenter,
AnchorLine::VerticalCenter);
}
}
//static void alignLine(QLineF &line) //static void alignLine(QLineF &line)
//{ //{
// line.setP1(QPointF(std::floor(line.p1().x()) + 0.5, // line.setP1(QPointF(std::floor(line.p1().x()) + 0.5,

View File

@@ -42,6 +42,12 @@ namespace QmlDesigner {
class Snapper class Snapper
{ {
public: public:
enum Snapping {
UseSnapping,
UseSnappingAndAnchoring,
NoSnapping
};
Snapper(); Snapper();
void setContainerFormEditorItem(FormEditorItem *formEditorItem); void setContainerFormEditorItem(FormEditorItem *formEditorItem);
@@ -73,6 +79,9 @@ public:
QList<QGraphicsItem*> generateSnappingLines(const QRectF &boundingRect, QList<QGraphicsItem*> generateSnappingLines(const QRectF &boundingRect,
QGraphicsItem *layerItem, QGraphicsItem *layerItem,
const QTransform &transform); const QTransform &transform);
void adjustAnchoringOfItem(FormEditorItem *formEditorItem);
protected: protected:
double snappedOffsetForLines(const SnapLineMap &snappingLineMap, double snappedOffsetForLines(const SnapLineMap &snappingLineMap,
double value) const; double value) const;

View File

@@ -92,6 +92,14 @@ public:
void removeAnchors(); void removeAnchors();
bool instanceHasAnchor(AnchorLine::Type sourceAnchorLineType) const; bool instanceHasAnchor(AnchorLine::Type sourceAnchorLineType) const;
bool instanceHasAnchors() const; bool instanceHasAnchors() const;
double instanceLeftAnchorLine() const;
double instanceTopAnchorLine() const;
double instanceRightAnchorLine() const;
double instanceBottomAnchorLine() const;
double instanceHorizontalCenterAnchorLine() const;
double instanceVerticalCenterAnchorLine() const;
double instanceAnchorLine(AnchorLine::Type anchorLine) const;
void setMargin(AnchorLine::Type sourceAnchorLineType, double margin) const; void setMargin(AnchorLine::Type sourceAnchorLineType, double margin) const;
bool instanceHasMargin(AnchorLine::Type sourceAnchorLineType) const; bool instanceHasMargin(AnchorLine::Type sourceAnchorLineType) const;
double instanceMargin(AnchorLine::Type sourceAnchorLineType) const; double instanceMargin(AnchorLine::Type sourceAnchorLineType) const;
@@ -101,8 +109,13 @@ public:
void fill(); void fill();
void centerIn(); void centerIn();
protected: bool checkForCycle(AnchorLine::Type anchorLineTyp, const QmlItemNode &sourceItem) const;
bool checkForHorizontalCycle(const QmlItemNode &sourceItem) const;
bool checkForVerticalCycle(const QmlItemNode &sourceItem) const;
QmlItemNode qmlItemNode() const; QmlItemNode qmlItemNode() const;
protected:
void beautify(); void beautify();
private: private:

View File

@@ -393,7 +393,52 @@ bool QmlAnchors::instanceHasAnchors() const
instanceHasAnchor(AnchorLine::Bottom) || instanceHasAnchor(AnchorLine::Bottom) ||
instanceHasAnchor(AnchorLine::HorizontalCenter) || instanceHasAnchor(AnchorLine::HorizontalCenter) ||
instanceHasAnchor(AnchorLine::VerticalCenter) || instanceHasAnchor(AnchorLine::VerticalCenter) ||
instanceHasAnchor(AnchorLine::Baseline); instanceHasAnchor(AnchorLine::Baseline);
}
double QmlAnchors::instanceLeftAnchorLine() const
{
return qmlItemNode().nodeInstance().position().x();
}
double QmlAnchors::instanceTopAnchorLine() const
{
return qmlItemNode().nodeInstance().position().y();
}
double QmlAnchors::instanceRightAnchorLine() const
{
return qmlItemNode().nodeInstance().position().x() + qmlItemNode().nodeInstance().size().width();
}
double QmlAnchors::instanceBottomAnchorLine() const
{
return qmlItemNode().nodeInstance().position().y() + qmlItemNode().nodeInstance().size().height();
}
double QmlAnchors::instanceHorizontalCenterAnchorLine() const
{
return (instanceLeftAnchorLine() + instanceRightAnchorLine()) / 2.0;
}
double QmlAnchors::instanceVerticalCenterAnchorLine() const
{
return (instanceBottomAnchorLine() + instanceTopAnchorLine()) / 2.0;
}
double QmlAnchors::instanceAnchorLine(AnchorLine::Type anchorLine) const
{
switch (anchorLine) {
case AnchorLine::Left: return instanceLeftAnchorLine();
case AnchorLine::Top: return instanceTopAnchorLine();
case AnchorLine::Bottom: return instanceBottomAnchorLine();
case AnchorLine::Right: return instanceRightAnchorLine();
case AnchorLine::HorizontalCenter: return instanceHorizontalCenterAnchorLine();
case AnchorLine::VerticalCenter: return instanceVerticalCenterAnchorLine();
default: return 0;
}
return 0.0;
} }
void QmlAnchors::setMargin(AnchorLine::Type sourceAnchorLineType, double margin) const void QmlAnchors::setMargin(AnchorLine::Type sourceAnchorLineType, double margin) const
@@ -407,6 +452,71 @@ bool QmlAnchors::instanceHasMargin(AnchorLine::Type sourceAnchorLineType) const
return !qIsNull(instanceMargin(sourceAnchorLineType)); return !qIsNull(instanceMargin(sourceAnchorLineType));
} }
static bool checkForHorizontalCycleRecusive(const QmlAnchors &anchors, QList<QmlItemNode> &visitedItems)
{
visitedItems.append(anchors.qmlItemNode());
if (anchors.instanceHasAnchor(AnchorLine::Left)) {
AnchorLine leftAnchorLine = anchors.instanceAnchor(AnchorLine::Left);
if (visitedItems.contains(leftAnchorLine.qmlItemNode()) || checkForHorizontalCycleRecusive(leftAnchorLine.qmlItemNode().anchors(), visitedItems))
return true;
}
if (anchors.instanceHasAnchor(AnchorLine::Right)) {
AnchorLine rightAnchorLine = anchors.instanceAnchor(AnchorLine::Right);
if (visitedItems.contains(rightAnchorLine.qmlItemNode()) || checkForHorizontalCycleRecusive(rightAnchorLine.qmlItemNode().anchors(), visitedItems))
return true;
}
if (anchors.instanceHasAnchor(AnchorLine::HorizontalCenter)) {
AnchorLine horizontalCenterAnchorLine = anchors.instanceAnchor(AnchorLine::HorizontalCenter);
if (visitedItems.contains(horizontalCenterAnchorLine.qmlItemNode()) || checkForHorizontalCycleRecusive(horizontalCenterAnchorLine.qmlItemNode().anchors(), visitedItems))
return true;
}
return false;
}
static bool checkForVerticalCycleRecusive(const QmlAnchors &anchors, QList<QmlItemNode> &visitedItems)
{
visitedItems.append(anchors.qmlItemNode());
if (anchors.instanceHasAnchor(AnchorLine::Top)) {
AnchorLine topAnchorLine = anchors.instanceAnchor(AnchorLine::Top);
if (visitedItems.contains(topAnchorLine.qmlItemNode()) || checkForVerticalCycleRecusive(topAnchorLine.qmlItemNode().anchors(), visitedItems))
return true;
}
if (anchors.instanceHasAnchor(AnchorLine::Bottom)) {
AnchorLine bottomAnchorLine = anchors.instanceAnchor(AnchorLine::Bottom);
if (visitedItems.contains(bottomAnchorLine.qmlItemNode()) || checkForVerticalCycleRecusive(bottomAnchorLine.qmlItemNode().anchors(), visitedItems))
return true;
}
if (anchors.instanceHasAnchor(AnchorLine::VerticalCenter)) {
AnchorLine verticalCenterAnchorLine = anchors.instanceAnchor(AnchorLine::VerticalCenter);
if (visitedItems.contains(verticalCenterAnchorLine.qmlItemNode()) || checkForVerticalCycleRecusive(verticalCenterAnchorLine.qmlItemNode().anchors(), visitedItems))
return true;
}
return false;
}
bool QmlAnchors::checkForHorizontalCycle(const QmlItemNode &sourceItem) const
{
QList<QmlItemNode> visitedItems;
visitedItems.append(sourceItem);
return checkForHorizontalCycleRecusive(*this, visitedItems);
}
bool QmlAnchors::checkForVerticalCycle(const QmlItemNode &sourceItem) const
{
QList<QmlItemNode> visitedItems;
visitedItems.append(sourceItem);
return checkForVerticalCycleRecusive(*this, visitedItems);
}
double QmlAnchors::instanceMargin(AnchorLine::Type sourceAnchorLineType) const double QmlAnchors::instanceMargin(AnchorLine::Type sourceAnchorLineType) const
{ {
return qmlItemNode().nodeInstance().property(marginPropertyName(sourceAnchorLineType)).toDouble(); return qmlItemNode().nodeInstance().property(marginPropertyName(sourceAnchorLineType)).toDouble();
@@ -452,4 +562,12 @@ void QmlAnchors::centerIn()
qmlItemNode().modelNode().bindingProperty("anchors.centerIn").setExpression("parent"); qmlItemNode().modelNode().bindingProperty("anchors.centerIn").setExpression("parent");
} }
bool QmlAnchors::checkForCycle(AnchorLine::Type anchorLineTyp, const QmlItemNode &sourceItem) const
{
if (anchorLineTyp & AnchorLine::HorizontalMask)
return checkForHorizontalCycle(sourceItem);
else
return checkForVerticalCycle(sourceItem);
}
} //QmlDesigner } //QmlDesigner