Browse Source

添加节点;
脑图布局;

chengxr 1 year ago
parent
commit
8cc8778f90

+ 0 - 1
QFD/CCanvas/CLineItem.cpp

@@ -6,7 +6,6 @@ CLineItem::CLineItem(const QPointF &startPos, const QPointF &endPos, QGraphicsIt
     : CPathItem(parent), m_startPos(startPos), m_endPos(endPos)
 {
     setNormalLineColor(Qt::gray);
-    setLineWidth(5);
     updatePath();
 }
 

+ 49 - 6
QFD/CCanvas/CMindView.cpp

@@ -13,8 +13,12 @@
 
 CMindView::CMindView(QWidget *parent) : QGraphicsView(new QGraphicsScene(), parent)
 {
-    m_mind = new CMind(this);
     setRenderHints(QPainter::Antialiasing);  // 抗锯齿
+
+    m_mind  = new CMind(this);
+    m_group = new QGraphicsItemGroup();
+    m_group->setFlags(QGraphicsItem::ItemIsMovable);
+    m_group->setHandlesChildEvents(false);
 }
 
 CMind *CMindView::mind() const
@@ -51,17 +55,16 @@ void CMindView::createSubNode(int pNumber)
         return;
     }
     CNodeData data = root()->data();
-    CNodeData n    = CNodeData(data.projectId, data.evalType, 0, data.number);
+    CNodeData n    = CNodeData(data.projectId, data.evalType, m_mind->maxNumber() + 1, pNumber);
+
     addNode(n);
 }
 
 void CMindView::removeNode(int number)
 {
-    qDebug() << __FUNCTION__ << __LINE__ << endl;
     m_mind->removeNode(number);
     if (number == m_root->data().number) {
         clear();
-        qDebug() << __FUNCTION__ << __LINE__ << endl;
     } else {
         m_root->removeNode(number);
     }
@@ -83,18 +86,24 @@ void CMindView::refreshItems()
     }
     m_items.clear();
 
+    scene()->addItem(m_group);
+
+    refreshNodeGeometry(m_root);
     collectItems(m_root);
     for (QGraphicsItem *item : m_items) {
-        scene()->addItem(item);
+        m_group->addToGroup(item);
     }
+
+    QRectF r = m_group->childrenBoundingRect();
+    setSceneRect(QRectF(QPointF(), r.size()));
 }
 
 void CMindView::collectItems(CNodeItem *node)
 {
-    m_items.clear();
     if (node == nullptr) {
         return;
     }
+
     m_items.append(node->rectItem());
     for (QObject *o : node->children()) {
         CNodeItem *n = dynamic_cast<CNodeItem *>(o);
@@ -131,3 +140,37 @@ CNodeItem *CMindView::root() const
 {
     return m_root;
 }
+
+void CMindView::refreshNodeGeometry(CNodeItem *node, QPointF topLeft)
+{  /// 边框
+    QRect borderRect = QRect(topLeft.x(), topLeft.y() + (node->treeHeight() - node->borderHeight()) / 2,
+                             node->borderWidth(), node->borderHeight());
+    node->rectItem()->setRect(borderRect);
+
+    /// 文本
+    QPointF textPos = QPointF(borderRect.left() + m_hNodeSpace,
+                              borderRect.top() + (node->borderHeight() - node->textHeight()) / 2);
+    node->textItem()->setPos(textPos);
+
+    node->rectItem()->setPos(QPoint(0, 0));
+
+    /// 子节点
+    int x = borderRect.right() + m_hNodeSpace;
+    if (m_align) {
+        x = borderRect.left() + m_root->maxBorderWidthOfLevel(node->depth()) + m_hNodeSpace;
+    }
+
+    int y = topLeft.y();
+    if (node->borderHeight() > node->childrenHeight()) {
+        y += (node->borderHeight() - node->childrenHeight()) / 2;
+    }
+
+    for (QObject *obj : node->children()) {
+        CNodeItem *subNode = dynamic_cast<CNodeItem *>(obj);
+        refreshNodeGeometry(subNode, QPointF(x, y));
+        y += subNode->treeHeight() + m_vNodeSpace;
+
+        subNode->lineItem()->setStartPos(node->rectItem()->centerRight());
+        subNode->lineItem()->setEndPos(subNode->rectItem()->centerLeft());
+    }
+}

+ 4 - 0
QFD/CCanvas/CMindView.h

@@ -46,11 +46,15 @@ public:
 
     CNodeItem *root() const;
 
+    void refreshNodeGeometry(CNodeItem *node, QPointF topLeft = QPointF());
+
 private:
     CMind *m_mind = nullptr;
 
     CNodeItem *m_root = nullptr;
 
+    QGraphicsItemGroup *m_group = nullptr;
+
     // 场景中显示的项目
     QList<QGraphicsItem *> m_items;
 

+ 120 - 0
QFD/CCanvas/CNodeItem.cpp

@@ -61,6 +61,11 @@ CTextItem *CNodeItem::textItem() const
     return m_textItem;
 }
 
+CLineItem *CNodeItem::lineItem() const
+{
+    return m_lineItem;
+}
+
 QPointF CNodeItem::pos() const
 {
     return m_pos;
@@ -133,6 +138,121 @@ void CNodeItem::updateItemsGeometry()
     m_textItem->setPos(m_pos + QPointF(m_xMargin, (h - tr.height()) / 2));
 }
 
+int CNodeItem::height() const
+{
+    int h = 1;
+
+    for (QObject *obj : children()) {
+        CNodeItem *item = dynamic_cast<CNodeItem *>(obj);
+        h               = std::max(h, item->height() + 1);
+    }
+
+    return h;
+}
+
+int CNodeItem::depth() const
+{
+    int d                 = 1;
+    const CNodeItem *node = this;
+
+    while (node->parent() != nullptr) {
+        d++;
+        node = dynamic_cast<CNodeItem *>(node->parent());
+    }
+
+    return d;
+}
+
+int CNodeItem::leafs() const
+{
+    int d = 1;
+    if (children().count() > 0) {
+        int d = 0;
+
+        for (QObject *obj : children()) {
+            CNodeItem *item = dynamic_cast<CNodeItem *>(obj);
+            d += item->leafs();
+        }
+    }
+
+    return d;
+}
+
+int CNodeItem::sizeOfLevel(int lev) const
+{
+    if (lev < 1) {
+        return 0;
+    }
+
+    if (lev == 1) {
+        return 1;
+    }
+
+    int size = 0;
+    for (QObject *obj : children()) {
+        CNodeItem *item = dynamic_cast<CNodeItem *>(obj);
+        size += item->sizeOfLevel(lev - 1);
+    }
+
+    return size;
+}
+
+qreal CNodeItem::textWidth() const
+{
+    return m_textItem->boundingRect().width();
+}
+
+qreal CNodeItem::textHeight() const
+{
+    return m_textItem->boundingRect().height();
+}
+
+qreal CNodeItem::borderWidth() const
+{
+    return std::max(textWidth() + m_xMargin * 2, m_minWidth);
+}
+
+qreal CNodeItem::borderHeight() const
+{
+    return std::max(textHeight() + m_yMargin * 2, m_minHeight);
+}
+
+qreal CNodeItem::childrenHeight() const
+{
+    qreal h = 20 * (children().count() - 1);  // 子节点竖直方向上总间隔
+    for (QObject *obj : children()) {
+        CNodeItem *item = dynamic_cast<CNodeItem *>(obj);
+        h += item->treeHeight();  // 加上各子节点高度
+    }
+
+    return std::max(h, h - h);
+}
+
+qreal CNodeItem::treeHeight() const
+{
+    return std::max(borderHeight(), childrenHeight());
+}
+
+qreal CNodeItem::maxBorderWidthOfLevel(int lev) const
+{
+    if (lev < 1) {
+        return 0;
+    }
+
+    if (lev == 1) {
+        return borderWidth();
+    }
+
+    qreal w = 0;
+
+    for (QObject *obj : children()) {
+        CNodeItem *item = dynamic_cast<CNodeItem *>(obj);
+        w               = std::max(w, item->maxBorderWidthOfLevel(lev - 1));
+    }
+
+    return w;
+}
+
 void CNodeItem::slotSelect()
 {
     qDebug() << __FUNCTION__ << __LINE__ << endl;

+ 33 - 1
QFD/CCanvas/CNodeItem.h

@@ -24,6 +24,7 @@ public:
 
     CRectItem *rectItem() const;
     CTextItem *textItem() const;
+    CLineItem *lineItem() const;
 
     QPointF pos() const;
     void setPos(const QPointF pos);
@@ -42,9 +43,40 @@ public:
 
     void connectSignalsAndSlots();
 
-private:
     void updateItemsGeometry();
 
+    int height() const;  // 节点的高度
+
+    int depth() const;  // 节点的深度
+
+    int leafs() const;  // 叶子节点个数或包含的路径条数
+
+    ///
+    /// \brief sizeOfLevel 以此节点为根节点的子树中,某一层的节点数
+    /// \param lev 节点层级,当前节点为1,向下递增
+    /// \return 节点数
+    ///
+    int sizeOfLevel(int lev) const;
+
+    qreal textWidth() const;
+    qreal textHeight() const;
+
+    qreal borderWidth() const;
+    qreal borderHeight() const;
+
+    /// 子节点的布局高度
+    qreal childrenHeight() const;
+
+    /// 以此节点为根节点的子树的布局高度
+    qreal treeHeight() const;
+
+    ///
+    /// \brief maxBorderWidthOfLevel 以此节点为根节点的子树中,某一层所有同辈节点中的最大边框宽度
+    /// \param lev 节点层级,当前节点为1,向下递增
+    /// \return 边框宽度
+    ///
+    qreal maxBorderWidthOfLevel(int lev = 1) const;
+
 signals:
     void sigAddSubItem(int pNumber);
     void sigRemoveItem(int number);

+ 1 - 1
QFD/CCanvas/CPathItem.h

@@ -32,7 +32,7 @@ private:
     void applySettings();
 
 private:
-    int m_lineWidth = 2;
+    int m_lineWidth = 1;
 
     QColor m_normalLineColor    = Qt::gray;
     QColor m_highlightLineColor = Qt::blue;

+ 12 - 7
QFD/CCanvas/CRectItem.cpp

@@ -12,8 +12,8 @@ CRectItem::CRectItem(const QRectF &rect, QGraphicsItem *parent) : CPathItem(pare
     updatePath();
 
     m_menu    = new QMenu();
-    m_select  = m_menu->addAction("选中");
     m_subNode = m_menu->addAction("添加子节点");
+    m_select  = m_menu->addAction("选中");
     m_remove  = m_menu->addAction("删除");
 }
 
@@ -28,6 +28,16 @@ void CRectItem::setRect(const QRectF &rect)
     updatePath();
 }
 
+QPointF CRectItem::centerLeft() const
+{
+    return QPointF(m_rect.center() - QPointF(m_rect.width() / 2, 0));
+}
+
+QPointF CRectItem::centerRight() const
+{
+    return QPointF(m_rect.center() + QPointF(m_rect.width() / 2, 0));
+}
+
 int CRectItem::cornerRadius() const
 {
     return m_cornerRadius;
@@ -48,17 +58,12 @@ void CRectItem::updatePath()
 
 void CRectItem::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
 {
-    qDebug() << __FUNCTION__ << __LINE__ << endl;
-
     const QString txt = highlighted() ? "取消选中" : "选中";
     m_select->setText(txt);
     m_menu->exec(event->screenPos());
 }
 
-void CRectItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
-{
-    qDebug() << __FUNCTION__ << __LINE__ << endl;
-}
+void CRectItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { }
 
 QAction *CRectItem::selectAction() const
 {

+ 4 - 0
QFD/CCanvas/CRectItem.h

@@ -17,6 +17,10 @@ public:
 
     void setRect(const QRectF &rect);
 
+    QPointF centerLeft() const;
+
+    QPointF centerRight() const;
+
     int cornerRadius() const;
 
     void setCornerRadius(qreal radius);

+ 5 - 5
QFD/widgets/IndexSystemWidget.cpp

@@ -34,17 +34,17 @@ void IndexSystemWidget::contextMenuEvent(QContextMenuEvent *event)
 
     QMenu *menu = new QMenu();
 
+    if (m_mindView->root() == nullptr) {
+        QAction *act3 = menu->addAction("创建根节点");
+        connect(act3, &QAction::triggered, this, &IndexSystemWidget::slotCreateRootNode);
+    }
+
     QAction *act1 = menu->addAction("全选");
     connect(act1, &QAction::triggered, this, &IndexSystemWidget::slotSelectAllNodes);
 
     QAction *act2 = menu->addAction("清空");
     connect(act2, &QAction::triggered, this, &IndexSystemWidget::slotClearAllNodes);
 
-    if (m_mindView->root() == nullptr) {
-        QAction *act3 = menu->addAction("创建根节点");
-        connect(act3, &QAction::triggered, this, &IndexSystemWidget::slotCreateRootNode);
-    }
-
     menu->exec(event->globalPos());
 
     QWidget::contextMenuEvent(event);