Revision control
Copy as Markdown
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QtGui/QCursor>
#include <QtWidgets/QGraphicsSceneMouseEvent>
#include <QtWidgets/QGraphicsSceneHoverEvent>
#include <QtWidgets/QGraphicsScene>
#include <QtCharts/QLegendMarker>
#include <QtCharts/QChart>
#include <private/legendlayout_p.h>
#include <private/legendmoveresizehandler_p.h>
QT_BEGIN_NAMESPACE
LegendMoveResizeHandler::LegendMoveResizeHandler(QLegend *legend) :
m_legend(legend)
{
m_legend->setAcceptHoverEvents(true);
m_legend->setCursor(Qt::ArrowCursor);
}
LegendMoveResizeHandler::~LegendMoveResizeHandler()
{
}
void LegendMoveResizeHandler::reset()
{
m_action = Action::Idle;
setMouseCursor(MousePosition::Nowhere);
}
void LegendMoveResizeHandler::handleMousePressEvent(QGraphicsSceneMouseEvent *event)
{
determineMousePosition(event->pos());
m_moveOffset = event->pos();
m_action = Action::Pressed;
// Since the legend does not have a proper frame,
// with a title bar, we have replaced the "top" from
// being a resize bar to being a move area. This
// is evident to the user as the mouse cursor
// changes to an open hand while over the top
// in hover mode, and a closed hand when the mouse
// button is pressed.
if (m_legend->isAttachedToChart()) {
m_action = Action::Idle;
} else if (m_mode == MousePosition::Top) {
m_action = Action::Moving;
setMouseCursor();
} else if (m_mode != MousePosition::Nowhere) {
m_action = Action::Resizing;
}
}
void LegendMoveResizeHandler::handleMouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
QRectF geom = m_legend->geometry();
QMarginsF reattachMargins{m_reattachThreshold, m_reattachThreshold, m_reattachThreshold, m_reattachThreshold};
QRectF dragArea = m_legend->parentWidget()->geometry() - reattachMargins;
if (m_action == Action::Moving) {
bool reattach = true;
QPointF toPoint = event->scenePos() - m_moveOffset;
if (event->scenePos().x() <= dragArea.left())
m_legend->setAlignment(Qt::AlignLeft);
else if (event->scenePos().x() >= dragArea.right())
m_legend->setAlignment(Qt::AlignRight);
else if (event->scenePos().y() <= dragArea.top())
m_legend->setAlignment(Qt::AlignTop);
else if (event->scenePos().y() >= dragArea.bottom())
m_legend->setAlignment(Qt::AlignBottom);
else
reattach = false;
QRectF potentialGeom(geom);
potentialGeom.moveTopLeft(toPoint);
if (potentialGeom.left() <= dragArea.left())
toPoint.setX(dragArea.left());
else if (potentialGeom.right() >= dragArea.right())
toPoint.setX(dragArea.right() - geom.width());
if (potentialGeom.top() <= dragArea.top())
toPoint.setY(dragArea.top());
else if (potentialGeom.bottom() >= dragArea.bottom())
toPoint.setY(dragArea.bottom() - geom.height());
// Set the geometry to be the new desired position corrected to
// be within the bounds of the dragArea.
geom.moveTopLeft(toPoint);
if (geom != m_legend->geometry())
m_legend->setGeometry(geom);
if (reattach && !m_legend->isAttachedToChart()) {
m_action = Action::Idle;
m_mode = MousePosition::Nowhere;
setMouseCursor();
m_legend->attachToChart();
}
} else if (m_action == Action::Resizing) {
QPointF trackPoint = event->scenePos();
QRectF boundRect = m_legend->parentWidget()->geometry();
if (trackPoint.x() <= boundRect.left())
trackPoint.rx() = boundRect.left() + 1;
else if (trackPoint.x() >= boundRect.right())
trackPoint.rx() = boundRect.right() - 1;
if (trackPoint.y() <= boundRect.top())
trackPoint.ry() = boundRect.top() + 1;
else if (trackPoint.y() >= boundRect.bottom())
trackPoint.ry() = boundRect.bottom();
switch (m_mode) {
case MousePosition::TopLeft:
geom = QRectF(trackPoint, geom.bottomRight());
break;
case MousePosition::BottomRight:
geom = QRectF(geom.topLeft(), trackPoint);
break;
case MousePosition::BottomLeft:
geom = QRectF(QPointF(trackPoint.x(), geom.y()), QPointF(geom.right(), trackPoint.y()));
break;
case MousePosition::TopRight:
geom = QRectF(QPointF(geom.x(), trackPoint.y()), QPoint(trackPoint.x(), geom.bottom()));
break;
case MousePosition::Bottom:
geom = QRectF(geom.topLeft(), QPointF(geom.right(), trackPoint.y()));
break;
case MousePosition::Left:
geom = QRectF(QPointF(trackPoint.x(), geom.top()), geom.bottomRight());
break;
case MousePosition::Right:
geom = QRectF(geom.topLeft(), QPointF(trackPoint.x(), geom.bottom()));
break;
default:
break;
}
geom = QRectF(geom.topLeft(),
geom.size().expandedTo(m_legend->d_ptr->m_layout->minimumSize())
.boundedTo((boundRect & geom).size()));
if (geom.size() != m_legend->geometry().size())
m_legend->setGeometry(geom);
}
}
void LegendMoveResizeHandler::handleMouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
m_action = Action::Idle;
determineMousePosition(event->pos());
setMouseCursor();
}
void LegendMoveResizeHandler::handleHoverEnterEvent(QGraphicsSceneHoverEvent *event)
{
if (!m_legend->isAttachedToChart()) {
determineMousePosition(event->pos());
setMouseCursor();
}
m_action = Action::Hovered;
}
void LegendMoveResizeHandler::handleHoverMoveEvent(QGraphicsSceneHoverEvent *event)
{
if (!m_legend->isAttachedToChart()) {
determineMousePosition(event->pos());
setMouseCursor();
}
m_action = Action::Hovered;
}
void LegendMoveResizeHandler::handleHoverLeaveEvent(QGraphicsSceneHoverEvent *)
{
if (!m_legend->isAttachedToChart()) {
m_mode = MousePosition::Nowhere;
setMouseCursor();
}
m_action = Action::Idle;
}
void LegendMoveResizeHandler::setMouseCursor()
{
setMouseCursor(m_mode);
}
void LegendMoveResizeHandler::setMouseCursor(MousePosition mpos)
{
#ifdef QT_NO_CURSOR
Q_UNUSED(mpos);
return;
#else
const QList<QGraphicsItem*> items = m_legend->d_ptr->m_items->childItems();
for (const auto item : items) {
if (!item->hasCursor())
item->setCursor(Qt::ArrowCursor);
}
switch (mpos) {
case MousePosition::TopLeft:
case MousePosition::BottomRight:
m_legend->setCursor(Qt::SizeFDiagCursor);
break;
case MousePosition::BottomLeft:
case MousePosition::TopRight:
m_legend->setCursor(Qt::SizeBDiagCursor);
break;
case MousePosition::Top:
if (m_action == Action::Moving)
m_legend->setCursor(Qt::ClosedHandCursor);
else
m_legend->setCursor(Qt::OpenHandCursor);
break;
case MousePosition::Bottom:
m_legend->setCursor(Qt::SizeVerCursor);
break;
case MousePosition::Left:
case MousePosition::Right:
m_legend->setCursor(Qt::SizeHorCursor);
break;
case MousePosition::Nowhere:
m_legend->setCursor(Qt::ArrowCursor);
break;
}
#endif
}
void LegendMoveResizeHandler::determineMousePosition(QPointF fromPoint)
{
QRectF contentRect = m_legend->d_ptr->m_layout->contentsRect();
if (fromPoint.x() <= contentRect.left()) {
if (fromPoint.y() <= contentRect.top())
m_mode = MousePosition::TopLeft;
else if (fromPoint.y() >= contentRect.bottom())
m_mode = MousePosition::BottomLeft;
else
m_mode = MousePosition::Left;
} else if (fromPoint.x() > contentRect.left() && fromPoint.x() < contentRect.right()) {
if (fromPoint.y() <= contentRect.top())
m_mode = MousePosition::Top;
else if (fromPoint.y() >= contentRect.bottom())
m_mode = MousePosition::Bottom;
else
// This catches a corner case where the mouse
// position y is inside of the content rect
m_mode = MousePosition::Nowhere;
} else if (fromPoint.x() >= contentRect.left()) {
if (fromPoint.y() <= contentRect.top())
m_mode = MousePosition::TopRight;
else if (fromPoint.y() >= contentRect.bottom())
m_mode = MousePosition::BottomRight;
else
m_mode = MousePosition::Right;
} else {
m_mode = MousePosition::Nowhere;
}
}
QT_END_NAMESPACE