#include "WindowAppItemLink.h" #include "WindowAppPouScene.h" #include "WindowAppItemInterface.h" // 连接线段数量(正常情况下是五段线) #define LINK_LINE_NORMAL_COUNT 5 // 并行模式(二段线) #define LINK_LINE_PARALLEL_COUNT 2 WindowAppItemLink::WindowAppItemLink( WindowAppItemInterface* startItem, WindowAppItemInterface* endItem, LINK_MODE linkMode, QVector linePoints ) : m_startInfItem(startItem) , m_endInfItem(endItem) , m_mode(linkMode) { // 接收悬停事件,用于显示鼠标切换效果 this->setAcceptHoverEvents(true); // 设置风格,可选择但不可拖动 this->setFlag(QGraphicsItem::ItemIsSelectable, true); // this->setFlag(QGraphicsItem::ItemIsMovable, true); // 初始化连接线段 this->initLines(linePoints); // 2022-6-12,如果初始化时指定了线段坐标,则按照指定的坐标来初始化线段(主要用于反序列化的情况) if (linePoints.size() <= 0) { // 如果没有指定坐标,则还是根据起点和终点自动生成线段信息 this->updateLinkLinesAuto(); } // 初始化右键菜单 createContextMenu(); //// 将消息全部透传到Group中的成员中处理 //this->setHandlesChildEvents(false); } WindowAppItemLink::~WindowAppItemLink() { // 删除动态创建的Link连线 for (int i = 0; i < m_linkLines.size(); i++) { delete m_linkLines[i]; } // 清空线段组 m_linkLines.clear(); } /// /// 初始化连接线段 /// void WindowAppItemLink::initLines(QVector linePoints) { if (m_startInfItem == nullptr || m_endInfItem == nullptr) { qDebug() << "[Error] WindowAppItemLink::initLines - start or end InfItem is nullptr."; return; } // 如果是普通模式,则正常初始化五段线 if (this->m_mode == LINK_MODE::LINK_NORMAL) { m_nLineCount = LINK_LINE_NORMAL_COUNT; } else { m_nLineCount = LINK_LINE_PARALLEL_COUNT; } // 初始化需要绘制的线条 for (int i = 0; i < m_nLineCount; i++) { WindowAppItemLinkLine* line = nullptr; if (linePoints.size() <= 0) { line = new WindowAppItemLinkLine(QPointF(0,0), QPointF(0,0), this, m_mode); } else { line = new WindowAppItemLinkLine(linePoints[i].p1(), linePoints[i].p2(), this, m_mode); } // 设定索引值,用于线条移动时使用 line->m_nLineIndex = i; m_linkLines.push_back(line); //// 加入本控件组 //this->addToGroup(line); // 绑定线段移动消息,用于在拖动时其他线段进行联动 connect( line, &WindowAppItemLinkLine::lineMoveSignal, this, &WindowAppItemLink::onLineMove ); } // 设置第一个线段为起点线段 m_linkLines[0]->setLinkStart(true); // 设置最后一个线段为终点线段 m_linkLines[m_nLineCount - 1]->setLinkEnd(true); } /// /// 按照正常模式更新线段 /// void WindowAppItemLink::updateNormalLines() { if (m_linkLines.size() <= 0) { return; } // 通过五段线的方式来进行连接 // 五段线起点坐标 QPointF ptStart = m_startInfItem->line().p2(); ptStart = mapFromScene(m_startInfItem->mapToScene(ptStart)); // 五段线终点坐标 QPointF ptEnd = m_endInfItem->line().p1(); ptEnd = mapFromScene(m_endInfItem->mapToScene(ptEnd)); // 每条连接线的起点和终点 QPointF ptLineStart = ptStart; QPointF ptLineEnd; // 起点连接线的终点位置坐标 QPointF ptLinkStart; // 终点连接线的终点位置坐标 QPointF ptLinkEnd; // 1. 计算起点连接线(起点一定是输出接口) ptLineEnd = QPointF(ptStart.x() + LINK_START_LINE_SIZE, ptStart.y()); ptLinkStart = ptLineEnd; // 线条方向从起点向外 m_linkLines[0]->updateLine(ptStart, ptLineEnd); // 2. 计算终点连接线(终点一定是输入接口) ptLinkEnd = QPointF(ptEnd.x() - LINK_START_LINE_SIZE, ptEnd.y()); // 线条方向从外部向终点 m_linkLines[4]->updateLine(ptLinkEnd, ptEnd); // 3. 起点延伸线 ptLineStart = ptLineEnd; float ptLineEndY = ptLineStart.y() + (ptEnd.y() - ptStart.y()) / 2; ptLineEnd = QPointF(ptLineStart.x(), ptLineEndY); // 方向为从起点往外 m_linkLines[1]->updateLine(ptLineStart, ptLineEnd); // 4. 中间连接线 ptLineStart = ptLineEnd; float ptLineEndX = ptLineStart.x() + (ptLinkEnd.x() - ptLinkStart.x()); ptLineEnd = QPointF(ptLineEndX, ptLineStart.y()); // 方向为从起点方向到终点方向 m_linkLines[2]->updateLine(ptLineStart, ptLineEnd); // 5. 终点延伸线 ptLineStart = ptLineEnd; // 方向为从外部连向终点 m_linkLines[3]->updateLine(ptLineStart, ptLinkEnd); } /// /// 按照并行模式更新线段 /// void WindowAppItemLink::updateParallelLines() { if (m_linkLines.size() <= 0) { return; } // 通过二段竖线的方式来进行连接 // 终点坐标(终点接口的外侧点) QPointF ptEnd(m_endInfItem->line().p1()); ptEnd = mapFromScene(m_endInfItem->mapToScene(ptEnd)); ptEnd.setX(ptEnd.rx() - LINK_START_LINE_SIZE); // 起点坐标(并行母线端) QPointF ptStart(m_endInfItem->line().p1().rx() - LINK_START_LINE_SIZE, m_startInfItem->line().p2().ry()); ptStart = mapFromScene(m_startInfItem->mapToScene(ptStart)); ptStart.setX(ptEnd.rx()); // 1. 垂直连线(方向为从母线端到接口端,可能是向上,可能是向下) m_linkLines[0]->updateLine(ptStart, ptEnd); // 2. 终点连接线(终点一定是输入接口,方向为从左到右) ptEnd.setX(ptEnd.rx() + LINK_START_LINE_SIZE); QPointF ptLinkEnd = QPointF(ptEnd.rx() - LINK_START_LINE_SIZE, ptEnd.ry()); m_linkLines[1]->updateLine(ptLinkEnd, ptEnd); } //============================================================================= // // 菜单相关代码 // //============================================================================= /// /// 显示右键菜单 /// /// void WindowAppItemLink::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) { scene()->clearSelection(); this->setSelected(true); contextMenu->exec(event->screenPos()); } ///// ///// 任何一条子线段被选中时,都交给Group统一处理 ///// ///// //void WindowAppItemLink::onLineSelected(WindowAppItemLinkLine* selItem) //{ // //for (const auto& lineItem : m_linkLines) // //{ // // lineItem->setSelected(true); // // // qDebug() << "WindowAppItemLink::onLineSelected."; // //} // // this->setSelected(true); //} ///// ///// 显示右键菜单 ///// ///// //void WindowAppItemLink::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) //{ // scene()->clearSelection(); // setSelected(true); // // contextMenu->exec(event->screenPos()); //} /// /// 初始化功能块的右键菜单 /// void WindowAppItemLink::createContextMenu() { cutAction = new QAction(("Cut"), this); connect(cutAction, &QAction::triggered, this, &WindowAppItemLink::onMenuCut); copyAction = new QAction(QIcon(":/image/Copy.png"), ("Copy"), this); connect(copyAction, &QAction::triggered, this, &WindowAppItemLink::onMenuCopy); pasteAction = new QAction(("Paste"), this); connect(pasteAction, &QAction::triggered, this, &WindowAppItemLink::onMenuPaste); deleteAction = new QAction(QIcon(":/image/Delete.png"), ("&Delete"), this); deleteAction->setStatusTip(("Delete item from diagram")); connect(deleteAction, &QAction::triggered, this, &WindowAppItemLink::onMenuDelete); contextMenu = new QMenu(); contextMenu->addAction(cutAction); contextMenu->addAction(copyAction); contextMenu->addAction(pasteAction); contextMenu->addSeparator(); contextMenu->addAction(deleteAction); } /// /// 菜单 - Cut /// void WindowAppItemLink::onMenuCut() { QMessageBox::information(nullptr, "Vision Plus", "onMenuCut"); } /// /// 菜单 - Copy /// void WindowAppItemLink::onMenuCopy() { QMessageBox::information(nullptr, "Vision Plus", "onMenuCopy"); } /// /// 菜单 - Paste /// void WindowAppItemLink::onMenuPaste() { QMessageBox::information(nullptr, "Vision Plus", "onMenuPaste"); } /// /// 菜单 - Delete /// void WindowAppItemLink::onMenuDelete() { // 直接调用scene()中的删除方法来统一删除 ((WindowAppPouScene*)scene())->delLink(this); } //============================================================================= // // 线条拖动相关代码 // //============================================================================= /// /// Group内的线段准备拖拽时,是否允许其移动(必须在合法的范围内拖动) /// /// /// /// bool WindowAppItemLink::canMove(QPointF nextP1, QPointF nextP2) { Q_UNUSED(nextP1); Q_UNUSED(nextP2); return true; } /// /// 获取所有线段坐标(用于序列化) /// /// QVector WindowAppItemLink::getAllLinkLinePoints() { QVector allLines; for (const auto& one_line : m_linkLines) { allLines.push_back(one_line->line()); } return allLines; } /// /// 将Link设置为Movable(包括Group和所有的子线段) /// /// void WindowAppItemLink::setMovable(bool bEnable /*= true*/) { // 自身设置为 Movable this->setFlag(QGraphicsItem::ItemIsMovable, bEnable); // 所有子线段设置 for (auto& subLine : m_linkLines) { subLine->setFlag(QGraphicsItem::ItemIsMovable, bEnable); } } /// /// 当Link线段移动时,同步调整其他线段的位置 /// /// void WindowAppItemLink::onLineMove(WindowAppItemLinkLine* moveItem) { // qDebug() << "WindowAppItemLink::onLineMove"; QPointF moveLinePt1 = moveItem->mapToScene(moveItem->line().p1()); QPointF moveLinePt2 = moveItem->mapToScene(moveItem->line().p2()); // qDebug() << "moveLinePt1:" << moveLinePt1 << " moveLinePt2:" << moveLinePt2; // 普通模式(普通模式只允许1、2、3号线移动) if (m_mode == LINK_MODE::LINK_NORMAL) { this->moveNormalLines(moveItem, moveLinePt1, moveLinePt2); } // 并行模式(并行模式仅有0号线允许移动) else { this->moveParallelLines(moveItem, moveLinePt1, moveLinePt2); } } /// /// 线段被拖动时,同步移动其他线段(普通模式) /// /// /// /// void WindowAppItemLink::moveNormalLines(WindowAppItemLinkLine* item, QPointF movePt1, QPointF movePt2) { if (m_linkLines.size() <= 0) { return; } // 如果移动的是1号线(1号线只允许左右移动) if (item->m_nLineIndex == 1) { // 将0号线的pt2以及2号线的pt1设置成与1号线相同 m_linkLines[0]->setP2(mapFromScene(movePt1)); m_linkLines[2]->setP1(mapFromScene(movePt2)); } // 如果移动的是2号线(2号线只允许上下移动) else if (item->m_nLineIndex == 2) { // 将1号线的pt2以及3号线的pt1设置成与2号线相同 m_linkLines[1]->setP2(mapFromScene(movePt1)); m_linkLines[3]->setP1(mapFromScene(movePt2)); } // 如果移动的是3号线(3号线只允许左右移动) else if (item->m_nLineIndex == 3) { // 将2号线的pt2以及4号线的pt1设置成与2号线相同 m_linkLines[2]->setP2(mapFromScene(movePt1)); m_linkLines[4]->setP1(mapFromScene(movePt2)); } // Error else { } } /// /// 线段被拖动时,同步移动其他线段(并行模式) /// /// /// /// void WindowAppItemLink::moveParallelLines(WindowAppItemLinkLine* item, QPointF movePt1, QPointF movePt2) { Q_UNUSED(item); Q_UNUSED(movePt1); // 因为只有 0 号线可以拖动,那么直接调整1号线的长度即可 WindowAppItemLinkLine* ajustLine = m_linkLines.last(); ajustLine->setP1(mapFromScene(movePt2)); // 2022-6-8,此时应该一起调整并行母线的长度 m_startInfItem->updatePostion(); } /// /// 根据起点和终点信息更新连接线段信息(自动方式连接) /// void WindowAppItemLink::updateLinkLinesAuto() { if (m_mode == LINK_MODE::LINK_NORMAL) { this->updateNormalLines(); } else { this->updateParallelLines(); } } /// /// 根据移动的功能块进行对应的连接线断调整(手动方式连接) /// /// /// void WindowAppItemLink::updateLinkLinesManual(QGraphicsItem* pBlock, bool bStart) { // qDebug() << "WindowAppItemLink::updateLinkLines " << bStart; Q_UNUSED(pBlock); // 如果是普通模式 if (m_mode == LINK_MODE::LINK_NORMAL) { // 如果移动的是起点位置的功能块,那么移动0号线和1号线 if (bStart) { this->updateNormalLinesByStart(); } // 如果移动的是终点位置的功能块,那么移动4号线和3号线 else { this->updateNormalLinesByEnd(); } } // 如果是并行模式 else if( m_mode==LINK_MODE::LINK_PARALLEL) { // 如果移动的是起点位置的功能块 if (bStart) { this->updateParallelLinesByStart(); } // 如果移动的是终点位置的功能块 else { this->updateParallelLinesByEnd(); } } } ///// ///// 按照并行模式更新线段(手动连接模式,功能块拖动时) ///// ///// //void WindowAppItemLink::updateParallelLines(QGraphicsItem* pBlock) //{ // //} /// /// 按照正常模式更新线段(手动连接模式,功能块拖动时) /// /// void WindowAppItemLink::updateNormalLinesByStart() { if (m_linkLines.size() <= 0) { return; } // 移动0号线和1号线 // 计算0号线 QPointF ptLineStart = m_startInfItem->line().p2(); ptLineStart = mapFromScene(m_startInfItem->mapToScene(ptLineStart)); QPointF ptLineEnd = m_linkLines[0]->line().p2(); // 如果0号线的长度少于了最小长度,则整个线条重置 if (qAbs(ptLineEnd.x() - ptLineStart.x()) < LINK_START_LINE_SIZE) { updateLinkLinesAuto(); return; } // 否则重新计算0号线的y值 // QPointF ptLineEnd = QPointF(ptLineStart.x() + LINK_START_LINE_SIZE, ptLineStart.y()); ptLineEnd.setY(ptLineStart.y()); m_linkLines[0]->updateLine(ptLineStart, ptLineEnd); // 然后继续调整1号线 // 调整规则,p2始终不变,只调整p1 m_linkLines[1]->setP1(ptLineEnd); } /// /// 按照正常模式更新线段(手动连接模式,功能块拖动时) /// /// void WindowAppItemLink::updateNormalLinesByEnd() { if (m_linkLines.size() <= 0) { return; } // 移动4号线和3号线 // 计算4号线 QPointF ptLineEnd = m_endInfItem->line().p1(); ptLineEnd = mapFromScene(m_endInfItem->mapToScene(ptLineEnd)); QPointF ptLineStart = m_linkLines[4]->line().p1(); // 如果4号线的长度少于了最小长度,则整个线条重置 if (qAbs(ptLineEnd.x() - ptLineStart.x()) < LINK_START_LINE_SIZE) { updateNormalLines(); return; } // 否则重新计算4号线的y值 ptLineStart.setY(ptLineEnd.y()); m_linkLines[4]->updateLine(ptLineStart, ptLineEnd); // 然后继续调整3号线 // 调整规则,p1始终不变,只调整p2 m_linkLines[3]->setP2(ptLineStart); } /// /// 按照并行模式更新线段(手动连接模式,起点功能块拖动时) /// void WindowAppItemLink::updateParallelLinesByStart() { if (m_linkLines.size() <= 0) { return; } // 计算0号线 // 只是调整一下 p1 的y值即可 QPointF ptLineStart = m_linkLines[0]->line().p1(); QPointF ptItem = m_startInfItem->line().p2(); ptItem = mapFromScene(m_startInfItem->mapToScene(ptItem)); ptLineStart.setY(ptItem.ry()); m_linkLines[0]->setP1(ptLineStart); } /// /// 按照并行模式更新线段(手动连接模式,终点功能块拖动时) /// void WindowAppItemLink::updateParallelLinesByEnd() { if (m_linkLines.size() <= 0) { return; } // 计算1号线 // 需要同时调整1号线和0号线 // // 计算1号线 QPointF ptLineEnd = m_endInfItem->line().p1(); ptLineEnd = mapFromScene(m_endInfItem->mapToScene(ptLineEnd)); QPointF ptLineStart = m_linkLines[1]->line().p1(); // 如果1号线的长度少于了最小长度,则整个线条重置 if (qAbs(ptLineEnd.x() - ptLineStart.x()) < LINK_START_LINE_SIZE) { updateParallelLines(); return; } // 否则重新计算1号线的y值 ptLineStart.setY(ptLineEnd.y()); m_linkLines[1]->updateLine(ptLineStart, ptLineEnd); // 然后继续调整0号线 // 调整规则,p1始终不变,只调整p2 m_linkLines[0]->setP2(ptLineStart); }