首先看一下效果图
移动
Item移动的主要思想是改变Item下所有QCPItemPosition的位置来达到移动的目的
void DPLPlot::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
if (auto *item = itemAt(event->pos(), true)) {
emit mousePress(event); // 由于我们直接返回了,所以要负责将鼠标点击信号发送出去
// deselectAll();
mMousePress = true;
mLastPos = event->pos();
item->setSelected(true);
replot();
return; // 如果点击的是一个item直接返回,不然QCustomPlot会把事件传递给其它的层对象(例如:轴矩形)
}
}
QCustomPlot::mousePressEvent(event);
}
void DPLPlot::mouseMoveEvent(QMouseEvent *event)
{
QCustomPlot::mouseMoveEvent(event);
if (mMousePress) {
auto items = selectedItems();
foreach (auto *item, items) {
if (auto *sizeHandle = qobject_cast<QCPSizeHandle *>(item))
mSizeHandleManager->handleItemResize(sizeHandle, event->pos() - mLastPos); // 控制item缩放
else
mSizeHandleManager->handleItemMove(item, event->pos() - mLastPos); // 控制item移动
}
mLastPos = event->pos();
replot();
}
}
void DPLPlot::mouseReleaseEvent(QMouseEvent *event)
{
if (mMousePress) {
mMousePress = false;
if (auto *item = itemAt(event->pos(), true)) {
emit mouseReleaseEvent(event); // 由于我们直接返回了,所以要负责将鼠标点击信号发送出去
item->setSelected(false);
replot();
return;
}
}
QCustomPlot::mouseReleaseEvent(event);
}
如下所示:我们改变item的所有QCPItemPosition的位置
void QCPSizeHandleManager::handleItemMove(QCPAbstractItem *item, const QPointF &delta)
{
if (!item)
return;
auto positions = item->positions();
foreach (auto *position, positions)
position->setPixelPosition(position->pixelPosition() + delta);
}
缩放
与移动稍微不同的是,缩放只是移动一个QCPItemPosition的位置就可以了,不过为了让缩放点可视化,我们在缩放点位置新增了一个QCPSizeHandle,QCPSizeHandle是一个自定义的Item,如果你还不会自定义Item,请看上篇QCustomPlot之鼠标悬浮显示值(十一)
QCPSizeHandle::QCPSizeHandle(QCustomPlot *parentPlot)
: QCPAbstractItem(parentPlot),
position(createPosition(QLatin1String("position"))),
mHovered(false)
{
// position->setType(QCPItemPosition::ptAbsolute);
setBrush(QColor("#436EEE"));
setHoveredBrush(QColor("#1C86EE"));
setSelectedBrush(QColor("#3A5FCD"));
setSize(8);
}
QCPSizeHandle::~QCPSizeHandle()
{
}
void QCPSizeHandle::setBrush(const QBrush &brush)
{
mBrush = brush;
}
void QCPSizeHandle::setSelectedBrush(const QBrush &brush)
{
mSelectedBrush = brush;
}
void QCPSizeHandle::setHoveredBrush(const QBrush &brush)
{
mHoveredBrush = brush;
}
void QCPSizeHandle::setSize(double size)
{
mSize = size;
}
double QCPSizeHandle::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
{
Q_UNUSED(details)
if (onlySelectable && !mSelectable)
return -1;
QPointF itemPos = position->pixelPosition();
QRectF rect = QRectF(itemPos.x() - mSize * 0.5, itemPos.y() - mSize * 0.5, mSize, mSize);
bool filledRect = mBrush.style() != Qt::NoBrush && mBrush.color().alpha() != 0;
return rectDistance(rect, pos, filledRect);
}
void QCPSizeHandle::draw(QCPPainter *painter)
{
QRectF rect(-mSize * 0.5, -mSize * 0.5, mSize, mSize);
painter->translate(position->pixelPosition());
painter->setClipRect(rect);
painter->setPen(Qt::NoPen);
painter->setBrush(mainBrush());
painter->drawRect(rect);
}
QBrush QCPSizeHandle::mainBrush() const
{
return selected() ? mSelectedBrush : (mHovered ? mHoveredBrush : mBrush);
}
如下所示:我们将QCPSizeHandle锚定在Item的QCPItemPosition上,这样我们可以不必关心QCPSizeHandle的位置,它总是跟随QCPItemPosition移动
void QCPSizeHandleManager::addItem(QCPAbstractItem *item, bool showHandlesLines)
{
if (!item || mHandles.contains(item))
return;
if (item->positions().size() < 2) // 要改变item的大小,最起码要两个位置
return;
QList<QCPSizeHandle *> handles;
foreach (auto *position, item->positions()) {
handles.push_back(addHandleToPosition(position));
}
ControlItemData data;
data.showHandlesLines = showHandlesLines;
data.handles = handles;
mHandles.insert(item, data);
}
void QCPSizeHandleManager::handleItemResize(QCPSizeHandle *sizeHandle, const QPointF &delta)
{
if (!sizeHandle)
return;
auto *parentPosition = static_cast<QCPItemPosition *>(sizeHandle->position->parentAnchor());
if (!parentPosition)
return;
parentPosition->setPixelPosition(parentPosition->pixelPosition() + delta);
}
QCPSizeHandle *QCPSizeHandleManager::addHandleToPosition(QCPItemPosition *position)
{
auto *handle = new QCPSizeHandle(mParentPlot);
handle->position->setParentAnchor(position); // 设置QCPSizeHandle的父锚点为position
handle->setVisible(false);
handle->setLayer(QLatin1String("overlay"));
return handle;
}
同时为了绘制QCPSizeHandle之间的连线,我们让QCPSizeHandleManager继承自QCPLayerable,它需要重载applyDefaultAntialiasingHint
和draw
函数
void QCPSizeHandleManager::applyDefaultAntialiasingHint(QCPPainter *painter) const
{
applyAntialiasingHint(painter, mAntialiased, QCP::aeOther);
}
void QCPSizeHandleManager::draw(QCPPainter *painter)
{
QMapIterator<QCPAbstractItem *, ControlItemData> i(mHandles);
while (i.hasNext()) {
i.next();
auto data = i.value();
if (!data.showHandlesLines)
continue;
painter->setPen(data.connectHandlePen);
QVector<QPointF> lines;
foreach (auto *handle, data.handles)
lines.push_back(handle->position->pixelPosition());
painter->drawLines(lines);
// painter->drawPolyline(QPolygonF(lines));
}
}
完整的头文件
class QCP_LIB_DECL QCPSizeHandleManager : public QCPLayerable
{
Q_OBJECT
public:
explicit QCPSizeHandleManager(QCustomPlot *parent);
~QCPSizeHandleManager();
void addItem(QCPAbstractItem *item, bool showHandlesLines = false);
void addItems(const QList<QCPAbstractItem *> items, bool showHandlesLines = false);
public slots:
void handleItemMove(QCPAbstractItem *item, const QPointF &delta);
void handleItemResize(QCPSizeHandle *sizeHandle, const QPointF &delta);
protected:
struct ControlItemData {
bool showHandlesLines;
bool moveable;
bool resizeable;
QPen connectHandlePen;
QList<QCPSizeHandle*> handles;
ControlItemData();
};
QMap<QCPAbstractItem *, ControlItemData> mHandles;
QCPSizeHandle *addHandleToPosition(QCPItemPosition *position);
virtual void applyDefaultAntialiasingHint(QCPPainter *painter) const Q_DECL_OVERRIDE;
virtual void draw(QCPPainter *painter) Q_DECL_OVERRIDE;
};