#include #include "ZxBaseUtil.h" #include "nmObjDelete.h" #include "nmObjDeleteTool.h" #include "nmSingalCenter.h" #include "ZxPlot.h" #include "zxGmyBase.h" #include "nmObjLineCrack.h" #include "nmDataFracture.h" #include "nmPlotGraphicBinder.h" nmObjDeleteTool::nmObjDeleteTool() : nmObjToolBase() , m_pPlot(nullptr) { setText(tr("nObjDeleteTool")); m_oNot = NMOT_Delete; initDeleteCursor(); } nmObjDeleteTool::nmObjDeleteTool(ZxPlot* pPlot) : nmObjToolBase() , m_pPlot(pPlot) { setText(tr("nObjDeleteTool")); m_oNot = NMOT_Delete; nmDataLogFile::getInstance()->writeLog(" nmObjDeleteTool ++++++++ " + QString::number(m_oNot)); initDeleteCursor(); } nmObjDeleteTool::~nmObjDeleteTool() { // 视口可能已销毁,由 QPointer 避免访问失效窗口。 resetDeleteCursor(); } void nmObjDeleteTool::initDeleteCursor() { m_hadExplicitCursor = false; QPixmap icon(ZxBaseUtil::getDirOf(s_Dir_Res, "Icon") + "nmTrash.png"); QPixmap cursorPixmap(40, 40); cursorPixmap.fill(Qt::transparent); QPainter painter(&cursorPixmap); // 仅显示小垃圾桶,以中心作为点击热点,大小不受画布缩放影响。 painter.drawPixmap(cursorPixmap.rect(), icon, icon.rect()); painter.end(); m_deleteCursor = QCursor(cursorPixmap, 20, 20); } bool nmObjDeleteTool::updateDeleteCursor(const QPointF& pt) { // 与点击使用同一命中规则,仅在绘图视口内切换光标。 if (m_pObj != nullptr || QApplication::activeModalWidget() != nullptr || findNearestDeleteObj(pt) == nullptr) { resetDeleteCursor(); return false; } QWidget* viewport = getView()->viewport(); if (m_cursorViewport != viewport) { resetDeleteCursor(); m_cursorViewport = viewport; m_hadExplicitCursor = viewport->testAttribute(Qt::WA_SetCursor); m_previousCursor = viewport->cursor(); } viewport->setCursor(m_deleteCursor); return true; } void nmObjDeleteTool::resetDeleteCursor() { // 只恢复本工具设置的光标,不覆盖后来由其他工具设置的样式。 if (m_cursorViewport && m_cursorViewport->cursor().pixmap().cacheKey() == m_deleteCursor.pixmap().cacheKey()) { if (m_hadExplicitCursor) { m_cursorViewport->setCursor(m_previousCursor); } else { m_cursorViewport->unsetCursor(); } } m_cursorViewport = nullptr; } bool nmObjDeleteTool::onMouseMove(const QPointF& pt) { if (m_pObj != nullptr) { return nmObjToolBase::onMouseMove(pt); } // 命中时阻止图元自身的移动光标覆盖删除提示。 return updateDeleteCursor(pt); } bool nmObjDeleteTool::onRightDown(const QPointF& pt) { // 右键退出或打开菜单前恢复普通光标。 resetDeleteCursor(); return nmObjToolBase::onRightDown(pt); } void nmObjDeleteTool::onDeactivated() { // 切换活动工具时释放自定义光标。 resetDeleteCursor(); nmObjToolBase::onDeactivated(); } bool nmObjDeleteTool::onLeftDown(const QPointF& pt) { if (m_pObj != nullptr) { return nmObjToolBase::onLeftDown(pt); } // 命中时接管按下事件,防止区域标记等图元的移动工具抢先处理抬起事件。 return findNearestDeleteObj(pt) != nullptr; } bool nmObjDeleteTool::onLeftUp(const QPointF& pt) { if (m_pObj != nullptr) { return nmObjToolBase::onLeftUp(pt); } // 按抬起位置重新命中,使用与悬停提示相同的对象筛选和距离规则。 ZxObjBase* obj = findNearestDeleteObj(pt); if (obj != nullptr) { // 弹出确认框之前恢复,确认或取消后都不残留删除光标。 resetDeleteCursor(); nmSingalCenter::getInstance()->emitSigDeleteOneObj(obj->getName(), (void*)this); } return true; } ZxObjBase* nmObjDeleteTool::findNearestDeleteObj(const QPointF& pt) { ZxRenderView* view = getView(); if (m_pPlot == nullptr || view == nullptr) { return nullptr; } // 转为视口坐标后按 16 像素判断,保持不同缩放下的点击容差一致。 const QPointF mousePos = view->mapFromScene(pt); const double hitRadius = 16.0; double nearestDistanceSquared = hitRadius * hitRadius; ZxObjBase* nearestObj = nullptr; zxGmyBase geometry; for (int i = 0; i < m_pPlot->getObjCount(); ++i) { ZxObjBase* obj = m_pPlot->getObjByIndex(i); if (obj == nullptr || !obj->isVisible()) { continue; } const bool isFracture = _isSame("nObjLineFracture", obj->getTagName()); const bool isRegion = _isSame("nObjRegion", obj->getTagName()); if (!isFracture && !isRegion && !_isSame("nObjLineFault", obj->getTagName()) && !_isSame("nObjPointWell", obj->getTagName()) && !_isSame("nObjRegionMark", obj->getTagName())) { continue; } // 保留原有规则:未绑定数据及 DFN 生成的裂缝不参与单独删除。 if (isFracture) { nmObjLineCrack* fracture = dynamic_cast(obj); nmPlotGraphicBinder* binder = fracture ? fracture->getGraphicBinder() : nullptr; nmDataFracture* data = binder ? binder->getCrackDataFor(fracture) : nullptr; if (data == nullptr || data->getFractureType().getValue() == tr("DFN")) { continue; } } QVector points = obj->getAllPos(); if (points.isEmpty()) { continue; } for (int j = 0; j < points.count(); ++j) { points[j] = view->mapFromScene(points[j]); } double distance = QLineF(mousePos, points[0]).length(); double distanceSquared = distance * distance; // 裂缝、断层按开放折线计算,只有区域包含末点到首点的闭合边。 const int segmentCount = isRegion ? points.count() : points.count() - 1; for (int j = 0; j < segmentCount; ++j) { const QPointF& start = points[j]; const QPointF& end = points[(j + 1) % points.count()]; double segmentDistanceSquared; if (start == end) { // 重复节点按单点处理,避免零长度线段参与投影计算。 distance = QLineF(mousePos, start).length(); segmentDistanceSquared = distance * distance; } else { double t = 0.0; segmentDistanceSquared = geometry.pointSegmentDistanceSquared2D(mousePos, start, end, t); } distanceSquared = qMin(distanceSquared, segmentDistanceSquared); } // 范围内优先选择最近图元;距离相同时保留先命中的对象。 if (distanceSquared <= hitRadius * hitRadius && (nearestObj == nullptr || distanceSquared < nearestDistanceSquared)) { nearestDistanceSquared = distanceSquared; nearestObj = obj; } } return nearestObj; } void nmObjDeleteTool::onPaint(QPainter* painter, const ZxPaintParam& param) { if (m_pObj == nullptr) { // 缩放或图元变化后重新判断命中,不再在画布上绘制垃圾桶。 updateDeleteCursor(getCurrentPos()); } }