Browse Source

添加灰色聚类配置界面

Signed-off-by: codeClown <zhaomengshou@126.com>
codeClown 1 year ago
parent
commit
ac207a4bcd

+ 8 - 1
QFD/QFD.pro

@@ -111,8 +111,11 @@ SOURCES += \
     widgets/EvaluateOptionWidget.cpp \
     widgets/ExpertInfoWidget.cpp \
     widgets/ExpertListWidget.cpp \
+    widgets/GreyClusteringConfigWidget.cpp \
+    widgets/GreyClusteringItemDelegate.cpp \
     widgets/IndexSystemWidget.cpp \
     widgets/LoginWidget.cpp \
+    widgets/MultiLevelHeaderView.cpp \
     widgets/ProjectListWidget.cpp \
     widgets/ProjectStateWidget.cpp \
     widgets/RegisterWidget.cpp \
@@ -169,8 +172,11 @@ HEADERS += \
     widgets/EvaluateOptionWidget.h \
     widgets/ExpertInfoWidget.h \
     widgets/ExpertListWidget.h \
+    widgets/GreyClusteringConfigWidget.h \
+    widgets/GreyClusteringItemDelegate.h \
     widgets/IndexSystemWidget.h \
     widgets/LoginWidget.h \
+    widgets/MultiLevelHeaderView.h \
     widgets/ProjectListWidget.h \
     widgets/ProjectStateWidget.h \
     widgets/RegisterWidget.h \
@@ -203,7 +209,8 @@ else: unix:!android: target.path = /opt/$${TARGET}/bin
 RESOURCES += \
     resource.qrc
 
-FORMS +=
+FORMS += \
+    widgets/GreyClusteringConfigWidget.ui
 
 VERSION = "2.1.2.1"
 

+ 196 - 0
QFD/widgets/GreyClusteringConfigWidget.cpp

@@ -0,0 +1,196 @@
+#include "GreyClusteringConfigWidget.h"
+#include "GreyClusteringItemDelegate.h"
+#include "MultiLevelHeaderView.h"
+#include "ui_GreyClusteringConfigWidget.h"
+
+#include <QLineEdit>
+#include <QDebug>
+#include <QMessageBox>
+
+GreyClusteringConfigWidget::GreyClusteringConfigWidget(int grayNumber, QWidget *parent)
+    : QWidget(parent), ui(new Ui::GreyClusteringConfigWidget), m_grayNumber(grayNumber)
+{
+    ui->setupUi(this);
+
+    m_model = new QStandardItemModel();
+    ui->grayConfigTableView->setModel(m_model);
+
+    ui->stackedWidget->setCurrentIndex(1);
+    ui->grayNumber->setText(QString::number(grayNumber));
+    initFormLayout();
+    refreshTablView();
+
+    resize(800, 800);
+}
+
+GreyClusteringConfigWidget::~GreyClusteringConfigWidget()
+{
+    delete ui;
+}
+
+void GreyClusteringConfigWidget::initFormLayout()
+{
+    for (int i = 0; i < m_grayNumber; ++i) {
+        auto le = new QLineEdit;
+        le->setPlaceholderText("请输入类别");
+        le->setMinimumHeight(28);
+        le->setMinimumWidth(200);
+        ui->grayFormLayout->addRow(QString(tr("级别 %1:")).arg(i), le);
+    }
+
+    qDebug() << ui->grayFormLayout->rowCount();
+
+    connect(ui->confirmBtn, &QPushButton::clicked, this, &GreyClusteringConfigWidget::onConfirmClick);
+}
+
+/**
+#if 0  // 竖直表头
+    auto vHeader = new MultiLevelHeaderView(Qt::Vertical, 9, 3, t);
+    vHeader->setColumnWidth(0, 40);
+    vHeader->setColumnWidth(1, 40);
+    vHeader->setColumnWidth(2, 80);
+    vHeader->setCellSpan(0, 0, 9, 1);
+    vHeader->setCellSpan(0, 1, 1, 2);
+    vHeader->setCellSpan(1, 1, 3, 1);
+    vHeader->setCellSpan(1, 2, 1, 1);
+    vHeader->setCellSpan(2, 2, 1, 1);
+    vHeader->setCellSpan(3, 2, 1, 1);
+    vHeader->setCellSpan(4, 1, 1, 2);
+    vHeader->setCellSpan(5, 1, 1, 2);
+    vHeader->setCellSpan(6, 1, 1, 2);
+    vHeader->setCellSpan(7, 1, 2, 1);
+    vHeader->setCellSpan(7, 2, 1, 1);
+    vHeader->setCellSpan(8, 2, 1, 1);
+
+    vHeader->setCellText(0, 0, "火力");
+    vHeader->setCellText(0, 1, "火炮口径");
+    vHeader->setCellText(1, 1, "初速");
+    vHeader->setCellText(1, 2, "穿甲弹");
+    vHeader->setCellText(2, 2, "破甲弹");
+    vHeader->setCellText(3, 2, "榴弹");
+    vHeader->setCellText(4, 1, "首发命中率");
+    vHeader->setCellText(5, 1, "直射距离");
+    vHeader->setCellText(6, 1, "弹药基数");
+    vHeader->setCellText(7, 1, "时间");
+    vHeader->setCellText(7, 2, "静对静");
+    vHeader->setCellText(8, 2, "静对动");
+
+    t->setVerticalHeader(vHeader);
+#endif
+*/
+
+struct IndexItem
+{
+    QString name;
+    int row;
+    int col;
+    int rowSpan;
+    int colSpan;
+};
+
+void GreyClusteringConfigWidget::refreshTablView()
+{
+    int rows            = 9;
+    int cols            = 9;
+    const int nodeDepth = 3;  // 节点深度
+
+    m_grayNames   = QStringList { "较差类", "一般类", "较好类" };
+    QTableView *t = ui->grayConfigTableView;
+
+    t->setAlternatingRowColors(false);
+    t->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
+    t->horizontalHeader()->setStyleSheet("QHeaderView::section{background:rgb(244,244,244);color: black;}");
+    t->verticalHeader()->setStyleSheet("QHeaderView::section{background:rgb(244,244,244);color: black;}");
+    t->verticalHeader()->setDefaultAlignment(Qt::AlignCenter);
+    t->setSelectionMode(QAbstractItemView::SingleSelection);
+
+    // 设置表头
+    {
+        auto hHeader = new MultiLevelHeaderView(Qt::Horizontal, 2, nodeDepth + 3 + m_grayNames.size(), t);
+        hHeader->setCellSpan(0, 0, 2, nodeDepth);           // 指标占位
+        for (int n = 0; n < 1 + m_grayNames.size(); ++n) {  //  单位+灰度级占位
+            hHeader->setCellSpan(0, nodeDepth + n, 2, 1);
+        }
+        hHeader->setCellSpan(0, nodeDepth + 1 + m_grayNames.size(), 1, 2);  // 延拓值占位
+        hHeader->setCellSpan(1, nodeDepth + 1 + m_grayNames.size(), 1, 1);
+        hHeader->setCellSpan(1, nodeDepth + 1 + m_grayNames.size() + 1, 1, 1);
+
+        // 一级
+        hHeader->setCellText(0, 0, QString("指标"));
+        hHeader->setCellText(0, nodeDepth, QString("单位"));
+        for (int i = 0; i < m_grayNames.size(); ++i) {
+            hHeader->setCellText(0, nodeDepth + 1 + i, m_grayNames.at(i));
+        }
+        hHeader->setCellText(0, nodeDepth + 1 + m_grayNames.size(), QString("延拓值"));
+        // 二级
+        hHeader->setCellText(1, nodeDepth + 1 + m_grayNames.size(), "左");
+        hHeader->setCellText(1, nodeDepth + 2 + m_grayNames.size(), "右");
+
+        t->setHorizontalHeader(hHeader);
+    }
+
+    QVector<IndexItem> items;
+    items << IndexItem { "火力", 0, 0, 9, 1 };
+    items << IndexItem { "火炮口径", 0, 1, 1, 2 };
+    items << IndexItem { "初速", 1, 1, 3, 1 };
+    items << IndexItem { "穿甲弹", 1, 2, 1, 1 };
+    items << IndexItem { "破甲弹", 2, 2, 1, 1 };
+    items << IndexItem { "榴弹", 3, 2, 1, 1 };
+
+    items << IndexItem { "首发命中", 4, 1, 1, 2 };
+    items << IndexItem { "直射距离", 5, 1, 1, 2 };
+    items << IndexItem { "弹药基数", 6, 1, 1, 2 };
+    items << IndexItem { "时间", 7, 1, 2, 1 };
+    items << IndexItem { "静对静", 7, 2, 1, 1 };
+    items << IndexItem { "静对动", 8, 2, 1, 1 };
+    m_model->setColumnCount(nodeDepth + 3 + m_grayNames.size());
+    m_model->setRowCount(9);  // 最大9行
+
+    for (auto &item : items) {
+        auto s = new QStandardItem(item.name);
+        s->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
+        s->setTextAlignment(Qt::AlignCenter);
+        s->setToolTip(item.name);
+        m_model->setItem(item.row, item.col, s);
+        if (item.rowSpan > 1 || item.colSpan > 1) {
+            t->setSpan(item.row, item.col, item.rowSpan, item.colSpan);
+        }
+    }
+
+    for (int r = 0; r < m_model->rowCount(); ++r) {
+        for (int c = nodeDepth; c < m_model->columnCount(); ++c) {
+            auto s = new QStandardItem();
+            s->setTextAlignment(Qt::AlignCenter);
+            m_model->setItem(r, c, s);
+        }
+    }
+
+    for (int c = 0; c < m_grayNames.size(); ++c) {
+        t->setItemDelegateForColumn(c + nodeDepth + 1, new GreyClusteringItemDelegate());
+    }
+}
+
+void GreyClusteringConfigWidget::onConfirmClick()
+{
+    QStringList names;
+    for (int i = 0; i < m_grayNumber; ++i) {
+        QLayoutItem *item = ui->grayFormLayout->itemAt(i + 1, QFormLayout::FieldRole);
+        QLineEdit *le     = qobject_cast<QLineEdit *>(item->widget());
+        if (le) {
+            if (le->text().trimmed().isEmpty()) {
+                QMessageBox::warning(this, tr("警告"), tr("类别%1不能为空!").arg(i));
+                return;
+            }
+            names << le->text().trimmed();
+        }
+    }
+
+    if (names.toSet().size() != m_grayNumber) {
+        QMessageBox::warning(this, tr("警告"), tr("类别名不能重复!"));
+        return;
+    }
+    m_grayNames = names;
+
+    refreshTablView();
+    ui->stackedWidget->setCurrentIndex(1);
+}

+ 34 - 0
QFD/widgets/GreyClusteringConfigWidget.h

@@ -0,0 +1,34 @@
+#ifndef GREYCLUSTERINGCONFIGWIDGET_H
+#define GREYCLUSTERINGCONFIGWIDGET_H
+
+#include <QStandardItemModel>
+#include <QWidget>
+
+namespace Ui {
+class GreyClusteringConfigWidget;
+}
+
+class GreyClusteringConfigWidget : public QWidget
+{
+    Q_OBJECT
+
+public:
+    explicit GreyClusteringConfigWidget(int grayNumber, QWidget *parent = nullptr);
+    ~GreyClusteringConfigWidget();
+
+private:
+    void initFormLayout();
+    void refreshTablView();
+
+private slots:
+    void onConfirmClick();
+
+private:
+    Ui::GreyClusteringConfigWidget *ui;
+
+    int m_grayNumber;
+    QStringList m_grayNames;
+    QStandardItemModel *m_model;
+};
+
+#endif  // GREYCLUSTERINGCONFIGWIDGET_H

+ 183 - 0
QFD/widgets/GreyClusteringConfigWidget.ui

@@ -0,0 +1,183 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>GreyClusteringConfigWidget</class>
+ <widget class="QWidget" name="GreyClusteringConfigWidget">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>400</width>
+    <height>300</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Form</string>
+  </property>
+  <layout class="QGridLayout" name="gridLayout_2">
+   <item row="0" column="1">
+    <widget class="QStackedWidget" name="stackedWidget">
+     <property name="currentIndex">
+      <number>1</number>
+     </property>
+     <widget class="QWidget" name="categoryPage">
+      <layout class="QGridLayout" name="gridLayout">
+       <item row="0" column="1">
+        <spacer name="verticalSpacer_2">
+         <property name="orientation">
+          <enum>Qt::Vertical</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>20</width>
+           <height>112</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+       <item row="1" column="0">
+        <spacer name="horizontalSpacer">
+         <property name="orientation">
+          <enum>Qt::Horizontal</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>131</width>
+           <height>20</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+       <item row="1" column="1">
+        <layout class="QFormLayout" name="grayFormLayout">
+         <item row="0" column="0">
+          <widget class="QLabel" name="label">
+           <property name="font">
+            <font>
+             <family>微软雅黑 Light</family>
+             <pointsize>12</pointsize>
+             <weight>75</weight>
+             <bold>true</bold>
+            </font>
+           </property>
+           <property name="text">
+            <string>灰类数:</string>
+           </property>
+          </widget>
+         </item>
+         <item row="0" column="1">
+          <widget class="QLabel" name="grayNumber">
+           <property name="font">
+            <font>
+             <family>微软雅黑 Light</family>
+             <pointsize>12</pointsize>
+            </font>
+           </property>
+           <property name="text">
+            <string>0</string>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+       <item row="1" column="2">
+        <spacer name="horizontalSpacer_2">
+         <property name="orientation">
+          <enum>Qt::Horizontal</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>130</width>
+           <height>20</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+       <item row="2" column="1">
+        <layout class="QHBoxLayout" name="horizontalLayout">
+         <property name="topMargin">
+          <number>20</number>
+         </property>
+         <item>
+          <spacer name="horizontalSpacer_4">
+           <property name="orientation">
+            <enum>Qt::Horizontal</enum>
+           </property>
+           <property name="sizeHint" stdset="0">
+            <size>
+             <width>40</width>
+             <height>20</height>
+            </size>
+           </property>
+          </spacer>
+         </item>
+         <item>
+          <widget class="QPushButton" name="confirmBtn">
+           <property name="maximumSize">
+            <size>
+             <width>50</width>
+             <height>16777215</height>
+            </size>
+           </property>
+           <property name="text">
+            <string>确认</string>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+       <item row="3" column="1">
+        <spacer name="verticalSpacer">
+         <property name="orientation">
+          <enum>Qt::Vertical</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>20</width>
+           <height>111</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="configPage">
+      <layout class="QGridLayout" name="gridLayout_3">
+       <item row="1" column="0">
+        <spacer name="horizontalSpacer_3">
+         <property name="orientation">
+          <enum>Qt::Horizontal</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>199</width>
+           <height>20</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+       <item row="0" column="0" colspan="3">
+        <widget class="QTableView" name="grayConfigTableView"/>
+       </item>
+       <item row="1" column="2">
+        <widget class="QPushButton" name="saveBtn">
+         <property name="text">
+          <string>保存</string>
+         </property>
+        </widget>
+       </item>
+       <item row="1" column="1">
+        <widget class="QPushButton" name="clearBtn">
+         <property name="text">
+          <string>清除</string>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>

+ 41 - 0
QFD/widgets/GreyClusteringItemDelegate.cpp

@@ -0,0 +1,41 @@
+#include "GreyClusteringItemDelegate.h"
+
+#include <QMessageBox>
+
+GreyClusteringItemDelegate::GreyClusteringItemDelegate(QObject *parent) : QStyledItemDelegate(parent) { }
+
+QWidget *GreyClusteringItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem & /* option */,
+                                                  const QModelIndex & /* index */) const
+{
+    RangeSpin *editor = new RangeSpin(parent);
+
+    return editor;
+}
+
+void GreyClusteringItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
+{
+    QString value      = index.model()->data(index, Qt::EditRole).toString();
+    RangeSpin *spinBox = static_cast<RangeSpin *>(editor);
+    spinBox->setValue(value);
+}
+
+void GreyClusteringItemDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
+                                              const QModelIndex &index) const
+{
+    RangeSpin *spinBox = static_cast<RangeSpin *>(editor);
+    if (!spinBox->valid()) {
+        QMessageBox::warning(nullptr, "警告", QString("设置的值%1区间错误,请重新设置!").arg(spinBox->result()));
+        return;
+    }
+    QString value = spinBox->result();
+
+    model->setData(index, value, Qt::EditRole);
+}
+
+void GreyClusteringItemDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option,
+                                                      const QModelIndex & /* index */) const
+{
+    QRect rect = option.rect;
+    rect.adjust(-10, -10, 10, 10);
+    editor->setGeometry(rect);
+}

+ 86 - 0
QFD/widgets/GreyClusteringItemDelegate.h

@@ -0,0 +1,86 @@
+#ifndef GREYCLUSTERINGITEMDELEGATE_H
+#define GREYCLUSTERINGITEMDELEGATE_H
+
+#include <QStyledItemDelegate>
+
+#include <QHBoxLayout>
+#include <QLabel>
+#include <QDoubleSpinBox>
+#include <QPushButton>
+
+class RangeSpin : public QFrame
+{
+    Q_OBJECT
+public:
+    RangeSpin(QWidget *parent = nullptr) : QFrame(parent)
+    {
+        QHBoxLayout *hLayout = new QHBoxLayout;
+        setStyleSheet("RangeSpin{background-color:gray} QLabel{color:white}");
+        hLayout->setMargin(0);
+        hLayout->setSpacing(0);
+        QLabel *minLabel = new QLabel("小");
+        QLabel *maxLabel = new QLabel("大");
+        m_minSpin        = new QDoubleSpinBox;
+        m_maxSpin        = new QDoubleSpinBox;
+        m_minSpin->setRange(-1000, 1000);
+        m_maxSpin->setRange(-1000, 1000);
+
+        hLayout->addWidget(minLabel);
+        hLayout->addWidget(m_minSpin);
+        hLayout->addWidget(m_maxSpin);
+        hLayout->addWidget(maxLabel);
+        setLayout(hLayout);
+    }
+    virtual ~RangeSpin() { }
+
+    QString result()
+    {
+        this->onConfirm();
+        return m_result;
+    }
+
+    bool valid() const
+    {
+        if (m_minSpin->value() < m_maxSpin->value()) {
+            return true;
+        }
+
+        return false;
+    }
+
+    void setValue(const QString &value)
+    {
+        if (!value.isEmpty()) {
+            QStringList tmp = value.mid(1, value.size() - 2).replace(" ", "").split(",");
+            m_minSpin->setValue(tmp[0].toDouble());
+            m_maxSpin->setValue(tmp[1].toDouble());
+        }
+    }
+
+private slots:
+    void onConfirm() { m_result = QString("[%1, %2]").arg(m_minSpin->value()).arg(m_maxSpin->value()); }
+
+private:
+    QDoubleSpinBox *m_minSpin;
+    QDoubleSpinBox *m_maxSpin;
+
+    QString m_result;
+};
+
+class GreyClusteringItemDelegate : public QStyledItemDelegate
+{
+    Q_OBJECT
+
+public:
+    GreyClusteringItemDelegate(QObject *parent = 0);
+
+    QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
+
+    void setEditorData(QWidget *editor, const QModelIndex &index) const override;
+    void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override;
+
+    void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option,
+                              const QModelIndex &index) const override;
+};
+
+#endif  // GREYCLUSTERINGITEMDELEGATE_H

+ 700 - 0
QFD/widgets/MultiLevelHeaderView.cpp

@@ -0,0 +1,700 @@
+#include "MultiLevelHeaderView.h"
+
+#include <QAbstractTableModel>
+#include <QMap>
+#include <QPainter>
+#include <QMouseEvent>
+#include <QVariant>
+#include <QBrush>
+#include <qdrawutil.h>
+#include <set>
+
+/**
+ *
+ * QTableView tableView;
+    auto pView = &tableView;
+        pView->setContextMenuPolicy(Qt::CustomContextMenu);
+        bool horizontal = false;
+
+    pView->setContextMenuPolicy(Qt::CustomContextMenu);
+    bool horizontal = true;
+    if (horizontal) {
+        auto pHeader = new MultiLevelHeaderView(Qt::Horizontal, 3, 8, pView);
+        pHeader->setCellSpan(0, 0, 1, 4);
+        pHeader->setCellSpan(0, 4, 1, 4);
+        pHeader->setCellSpan(1, 0, 1, 2);
+        pHeader->setCellSpan(1, 2, 1, 2);
+        pHeader->setCellSpan(1, 4, 1, 2);
+        pHeader->setCellSpan(1, 6, 1, 2);
+        for (int i = 0; i < 8; i++) {
+            pHeader->setCellSpan(2, i, 1, 1);
+        }
+
+        // 一级
+        pHeader->setCellText(0, 0, QString(u8"横向尺寸"));
+        pHeader->setCellText(0, 4, QString(u8"纵向尺寸"));
+        // 二级
+        pHeader->setCellText(1, 0, QStringLiteral("极耳宽度"));
+        pHeader->setCellText(1, 2, QStringLiteral("极耳高度"));
+        pHeader->setCellText(1, 4, QStringLiteral("极片宽度"));
+        pHeader->setCellText(1, 6, QStringLiteral("极耳间距"));
+        // 三级
+        pHeader->setCellText(2, 0, QStringLiteral("CCD测量值"));
+        pHeader->setCellText(2, 1, QStringLiteral("真值"));
+        pHeader->setCellText(2, 2, QStringLiteral("CCD测量值"));
+        pHeader->setCellText(2, 3, QStringLiteral("真值"));
+        pHeader->setCellText(2, 4, QStringLiteral("CCD测量值"));
+        pHeader->setCellText(2, 5, QStringLiteral("真值"));
+        pHeader->setCellText(2, 6, QStringLiteral("CCD测量值"));
+        pHeader->setCellText(2, 7, QStringLiteral("真值"));
+
+        pHeader->setMinimumHeight(90);
+        for (int i = 0; i < 3; ++i)
+            pHeader->setRowHeight(i, 30);
+        // m_pHeader->setMinimumHeight(60);
+        pHeader->setSectionsClickable(false);
+        pHeader->setCellBackgroundColor(0, 0, 0xffcfcf);
+        pHeader->setCellBackgroundColor(0, 4, 0xcfcfcf);
+        pHeader->setCellBackgroundColor(1, 0, 0xcfcfcf);
+        pHeader->setCellBackgroundColor(1, 2, 0xcfcfcf);
+        pHeader->setCellBackgroundColor(1, 4, 0xcfcfcf);
+        pHeader->setCellBackgroundColor(1, 6, 0xcfcfcf);
+        for (int i = 0; i < 8; i++) {
+            pHeader->setCellBackgroundColor(2, i, 0xcfcfcf);
+        }
+
+        int rowCount      = 10;
+        auto m_pDataModel = new QStandardItemModel;
+        for (int i = 0; i < rowCount; i++) {
+            QList<QStandardItem *> items;
+            for (int j = 0; j < 8; j++) {
+                items.append(new QStandardItem);
+            }
+
+            m_pDataModel->appendRow(items);
+        }
+
+        pView->setModel(m_pDataModel);
+        pView->setHorizontalHeader(pHeader);
+    } else {
+        auto pHeader = new MultiLevelHeaderView(Qt::Vertical, 8, 3, pView);
+        pHeader->setCellSpan(0, 0, 4, 1);
+        pHeader->setCellSpan(4, 0, 4, 1);
+        pHeader->setCellSpan(0, 1, 2, 1);
+        pHeader->setCellSpan(2, 1, 2, 1);
+        pHeader->setCellSpan(4, 1, 2, 1);
+        pHeader->setCellSpan(6, 1, 2, 1);
+        for (int i = 0; i < 8; i++) {
+            pHeader->setCellSpan(i, 2, 1, 1);
+        }
+
+        // 一级
+        pHeader->setCellText(0, 0, QString(u8"横\n向\n尺\n寸"));
+        pHeader->setCellText(4, 0, QString(u8"纵\n向\n尺\n寸"));
+        // 二级
+        pHeader->setCellText(0, 1, QStringLiteral("极耳宽度"));
+        pHeader->setCellText(2, 1, QStringLiteral("极耳高度"));
+        pHeader->setCellText(4, 1, QStringLiteral("极片宽度"));
+        pHeader->setCellText(6, 1, QStringLiteral("极耳间距"));
+        // 三级
+        pHeader->setCellText(0, 2, QStringLiteral("CCD测量值"));
+        pHeader->setCellText(1, 2, QStringLiteral("真值"));
+        pHeader->setCellText(2, 2, QStringLiteral("CCD测量值"));
+        pHeader->setCellText(3, 2, QStringLiteral("真值"));
+        pHeader->setCellText(4, 2, QStringLiteral("CCD测量值"));
+        pHeader->setCellText(5, 2, QStringLiteral("真值"));
+        pHeader->setCellText(6, 2, QStringLiteral("CCD测量值"));
+        pHeader->setCellText(7, 2, QStringLiteral("真值"));
+
+        pHeader->setMinimumHeight(90);
+        pHeader->setMinimumWidth(30);
+        for (int i = 0; i < 3; ++i)
+            pHeader->setColumnWidth(i, 30);
+        for (int i = 0; i < 8; ++i)
+            pHeader->setRowHeight(i, 30);
+        // m_pHeader->setMinimumHeight(60);
+        pHeader->setSectionsClickable(false);
+
+        int rowCount      = 8;
+        auto m_pDataModel = new QStandardItemModel;
+        for (int i = 0; i < rowCount; i++) {
+            QList<QStandardItem *> items;
+            for (int j = 0; j < 6; j++) {
+                items.append(new QStandardItem);
+            }
+
+            m_pDataModel->appendRow(items);
+        }
+
+        pView->setModel(m_pDataModel);
+        pView->setVerticalHeader(pHeader);
+    }
+ */
+
+struct Cell
+{
+    int row    = 0;
+    int column = 0;
+    bool operator<(const Cell &oth) const
+    {
+        if (row < oth.row)
+            return true;
+        if (row == oth.row)
+            return column < oth.column;
+        return false;
+    }
+};
+
+class MultiLevelHeaderModel : public QAbstractTableModel
+{
+public:
+    MultiLevelHeaderModel(Qt::Orientation orientation, int rows, int cols, QObject *parent = 0);
+    virtual ~MultiLevelHeaderModel();
+
+public:
+    // override
+    virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
+    virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+    virtual int columnCount(const QModelIndex &parent = QModelIndex()) const override;
+    virtual QVariant data(const QModelIndex &index, int role) const override;
+    virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
+    virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
+    void setRowHeight(int row, int size);
+    int getRowHeight(int row) const;
+    void setColumnWidth(int col, int size);
+    int getColumnWidth(int col) const;
+    void setRootCell(int row, int column, int rootRow, int rootColumn);
+    bool getRootCell(int row, int column, int &rootCellRow, int &rootCellColumn) const;
+
+private:
+    Qt::Orientation m_orientation;
+    int m_rowCount;
+    int m_columnCount;
+    std::vector<int> m_rowSizes;
+    std::vector<int> m_columnSizes;
+    QMap<Cell, QMap<int, QVariant>> m_data;
+    QMap<Cell, Cell> m_rootCellMap;
+};
+
+MultiLevelHeaderModel::MultiLevelHeaderModel(Qt::Orientation orientation, int rows, int cols, QObject *parent)
+    : QAbstractTableModel(parent), m_orientation(orientation), m_rowCount(rows), m_columnCount(cols)
+{
+    m_rowSizes.resize(rows, 0);
+    m_columnSizes.resize(cols, 0);
+}
+
+MultiLevelHeaderModel::~MultiLevelHeaderModel() { }
+
+QModelIndex MultiLevelHeaderModel::index(int row, int column, const QModelIndex &parent) const
+{
+    if (!hasIndex(row, column, parent))
+        return QModelIndex();
+
+    return createIndex(row, column, nullptr);
+}
+
+int MultiLevelHeaderModel::rowCount(const QModelIndex &parent) const
+{
+    return m_rowCount;
+}
+int MultiLevelHeaderModel::columnCount(const QModelIndex &parent) const
+{
+    return m_columnCount;
+}
+
+QVariant MultiLevelHeaderModel::data(const QModelIndex &index, int role) const
+{
+    if (!index.isValid())
+        return QVariant();
+
+    if (index.row() >= m_rowCount || index.row() < 0 || index.column() >= m_columnCount || index.column() < 0)
+        return QVariant();
+
+    auto it = m_data.find({ index.row(), index.column() });
+    if (it != m_data.end()) {
+        auto it2 = it.value().find(role);
+        if (it2 != it.value().end())
+            return it2.value();
+    }
+
+    // default value
+    if (role == Qt::BackgroundRole)
+        return QColor(0xcfcfcf);
+
+    if (role == Qt::SizeHintRole) {
+        return QSize(m_columnSizes[index.column()], m_rowSizes[index.row()]);
+    }
+    return QVariant();
+}
+
+bool MultiLevelHeaderModel::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+    if (index.isValid()) {
+        QVariant value2;
+        if (role == COLUMN_SPAN_ROLE) {
+            int col  = index.column();
+            int span = value.toInt();
+            if (span > 0)  // span size should be more than 1, else nothing to do
+            {
+                if (col + span - 1 >= m_columnCount)  // span size should be less than whole columns,
+                    span = m_columnCount - col;
+                value2 = span;
+            }
+        } else if (role == ROW_SPAN_ROLE) {
+            int row  = index.row();
+            int span = value.toInt();
+            if (span > 0)  // span size should be more than 1, else nothing to do
+            {
+                if (row + span - 1 >= m_rowCount)
+                    span = m_rowCount - row;
+                value2 = span;
+            }
+        } else {
+            value2 = value;
+        }
+        if (value.isValid()) {
+            if (role == Qt::SizeHintRole) {
+                m_columnSizes[index.column()] = value.toSize().width();
+                m_rowSizes[index.row()]       = value.toSize().height();
+            } else {
+                auto &map = m_data[{ index.row(), index.column() }];
+                map.insert(role, value2);
+            }
+        }
+
+        return true;
+    }
+    return false;
+}
+
+Qt::ItemFlags MultiLevelHeaderModel::flags(const QModelIndex &index) const
+{
+    return Qt::NoItemFlags | QAbstractTableModel::flags(index);
+}
+
+void MultiLevelHeaderModel::setRowHeight(int row, int size)
+{
+    if (row >= 0 && row < m_rowCount) {
+        m_rowSizes[row] = size;
+        emit dataChanged(index(row, 0), index(row, m_columnCount - 1), QVector<int>() << Qt::SizeHintRole);
+    }
+}
+
+int MultiLevelHeaderModel::getRowHeight(int row) const
+{
+    if (row >= 0 && row < m_rowCount)
+        return m_rowSizes[row];
+    return 0;
+}
+
+void MultiLevelHeaderModel::setColumnWidth(int col, int size)
+{
+    if (col >= 0 && col < m_columnCount) {
+        m_columnSizes[col] = size;
+        emit dataChanged(index(0, col), index(m_rowCount - 1, col), QVector<int>() << Qt::SizeHintRole);
+    }
+}
+
+int MultiLevelHeaderModel::getColumnWidth(int col) const
+{
+    if (col >= 0 && col < m_columnCount)
+        return m_columnSizes[col];
+    return 0;
+}
+
+void MultiLevelHeaderModel::setRootCell(int row, int column, int rootRow, int rootColumn)
+{
+    Cell c { row, column };
+    auto it = m_rootCellMap.find(c);
+    assert(it == m_rootCellMap.end());
+    Cell rc { rootRow, rootColumn };
+    m_rootCellMap.insert(c, rc);
+}
+
+bool MultiLevelHeaderModel::getRootCell(int row, int column, int &rootCellRow, int &rootCellColumn) const
+{
+    Cell c { row, column };
+    auto it = m_rootCellMap.find(c);
+    if (it == m_rootCellMap.end())
+        return false;
+    rootCellRow    = it.value().row;
+    rootCellColumn = it.value().column;
+    return true;
+}
+
+MultiLevelHeaderView::MultiLevelHeaderView(Qt::Orientation orientation, int rows, int columns, QWidget *parent)
+    : QHeaderView(orientation, parent)
+{
+    // create header model
+    MultiLevelHeaderModel *m = new MultiLevelHeaderModel(orientation, rows, columns);
+
+    // set default size of item
+    if (orientation == Qt::Horizontal) {
+        for (int row = 0; row < rows; ++row)
+            m->setRowHeight(row, 20);
+        for (int col = 0; col < columns; ++col)
+            m->setColumnWidth(col, defaultSectionSize());
+    } else {
+        for (int row = 0; row < rows; ++row)
+            m->setRowHeight(row, defaultSectionSize());
+        for (int col = 0; col < columns; ++col)
+            m->setColumnWidth(col, defaultSectionSize());
+    }
+
+    setModel(m);
+
+    connect(this, SIGNAL(sectionResized(int, int, int)), this, SLOT(onSectionResized(int, int, int)));
+}
+
+MultiLevelHeaderView::~MultiLevelHeaderView()
+{
+    MultiLevelHeaderModel *m = static_cast<MultiLevelHeaderModel *>(model());
+    if (m)
+        delete m;
+    setModel(nullptr);
+}
+
+void MultiLevelHeaderView::setRowHeight(int row, int rowHeight)
+{
+    MultiLevelHeaderModel *m = static_cast<MultiLevelHeaderModel *>(model());
+
+    m->setRowHeight(row, rowHeight);
+    if (orientation() == Qt::Vertical)
+        resizeSection(row, rowHeight);
+}
+
+void MultiLevelHeaderView::setColumnWidth(int col, int colWidth)
+{
+    MultiLevelHeaderModel *m = static_cast<MultiLevelHeaderModel *>(model());
+    m->setColumnWidth(col, colWidth);
+    if (orientation() == Qt::Horizontal)
+        resizeSection(col, colWidth);
+}
+
+void MultiLevelHeaderView::setCellData(int row, int column, int role, const QVariant &value)
+{
+    MultiLevelHeaderModel *m = static_cast<MultiLevelHeaderModel *>(model());
+    m->setData(m->index(row, column), value, role);
+}
+
+void MultiLevelHeaderView::setCellSpan(int row, int column, int rowSpanCount, int columnSpanCount)
+{
+    assert(rowSpanCount > 0);
+    assert(columnSpanCount > 0);
+    MultiLevelHeaderModel *m = static_cast<MultiLevelHeaderModel *>(model());
+    QModelIndex idx          = m->index(row, column);
+    m->setData(idx, rowSpanCount, ROW_SPAN_ROLE);
+    m->setData(idx, columnSpanCount, COLUMN_SPAN_ROLE);
+    int lastRow = row + rowSpanCount;
+    int lastCol = column + columnSpanCount;
+    for (int i = row; i < lastRow; ++i) {
+        for (int j = column; j < lastCol; ++j) {
+            m->setRootCell(i, j, row, column);
+        }
+    }
+}
+
+void MultiLevelHeaderView::setCellBackgroundColor(int row, int column, const QColor &color)
+{
+    MultiLevelHeaderModel *m = static_cast<MultiLevelHeaderModel *>(model());
+    m->setData(m->index(row, column), color, Qt::BackgroundRole);
+}
+
+void MultiLevelHeaderView::setCellForegroundColor(int row, int column, const QColor &color)
+{
+    MultiLevelHeaderModel *m = static_cast<MultiLevelHeaderModel *>(model());
+    m->setData(m->index(row, column), color, Qt::ForegroundRole);
+}
+
+void MultiLevelHeaderView::setCellText(int row, int column, const QString &text)
+{
+    MultiLevelHeaderModel *m = static_cast<MultiLevelHeaderModel *>(model());
+    m->setData(m->index(row, column), text, Qt::DisplayRole);
+}
+
+void MultiLevelHeaderView::mousePressEvent(QMouseEvent *event)
+{
+    QHeaderView::mousePressEvent(event);
+    QPoint pos        = event->pos();
+    QModelIndex index = indexAt(pos);
+    const int orient  = orientation();
+    if (index.isValid()) {
+        int beginSection = -1;
+        int endSection   = -1;
+        int numbers      = 0;
+        numbers          = getSectionRange(index, &beginSection, &endSection);
+        if (numbers > 0) {
+            emit sectionPressed(beginSection, endSection);
+            return;
+        } else {
+            const MultiLevelHeaderModel *m = static_cast<MultiLevelHeaderModel *>(this->model());
+            const int levelCount           = (orient == Qt::Horizontal) ? m->rowCount() : m->columnCount();
+            int logicalIdx                 = (orient == Qt::Horizontal) ? index.column() : index.row();
+            int curLevel                   = (orient == Qt::Horizontal) ? index.row() : index.column();
+            for (int i = 0; i < levelCount; ++i) {
+                QModelIndex cellIndex = (orient == Qt::Horizontal) ? m->index(i, logicalIdx) : m->index(logicalIdx, i);
+                numbers               = getSectionRange(cellIndex, &beginSection, &endSection);
+                if (numbers > 0) {
+                    if (beginSection <= logicalIdx && logicalIdx <= endSection) {
+                        int beginLevel = (orient == Qt::Horizontal) ? cellIndex.row() : cellIndex.column();
+                        QVariant levelSpanCnt =
+                                cellIndex.data((orient == Qt::Horizontal) ? ROW_SPAN_ROLE : COLUMN_SPAN_ROLE);
+                        if (!levelSpanCnt.isValid())
+                            continue;
+                        int endLevel = beginLevel + levelSpanCnt.toInt() - 1;
+                        if (beginLevel <= curLevel && curLevel <= endLevel) {
+                            emit sectionPressed(beginSection, endSection);
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+QModelIndex MultiLevelHeaderView::indexAt(const QPoint &pos) const
+{
+    const MultiLevelHeaderModel *m = static_cast<MultiLevelHeaderModel *>(this->model());
+    const int orient               = orientation();
+    const int ROWS                 = m->rowCount();
+    const int COLS                 = m->columnCount();
+    int logicalIdx                 = logicalIndexAt(pos);
+
+    if (orient == Qt::Horizontal) {
+        int dY = 0;
+        for (int row = 0; row < ROWS; ++row) {
+            dY += m->getRowHeight(row);
+            if (pos.y() <= dY) {
+                QModelIndex cellIndex = m->index(row, logicalIdx);
+                return cellIndex;
+            }
+        }
+    } else {
+        int dX = 0;
+        for (int col = 0; col < COLS; ++col) {
+            dX += m->getColumnWidth(col);
+            if (pos.x() <= dX) {
+                QModelIndex cellIndex = m->index(logicalIdx, col);
+                return cellIndex;
+            }
+        }
+    }
+
+    return QModelIndex();
+}
+
+std::set<Cell> getCellsToBeDrawn(const MultiLevelHeaderView *view, const MultiLevelHeaderModel *m, int orient,
+                                 int levelCount, int logicalIdx)
+{
+    std::set<Cell> cellsToBeDrawn;
+    for (int i = 0; i < levelCount; ++i) {
+        int row = i, column = logicalIdx;
+        if (orient == Qt::Vertical)
+            std::swap(row, column);
+        int rootRow, rootCol;
+        if (m->getRootCell(row, column, rootRow, rootCol))
+            cellsToBeDrawn.insert({ rootRow, rootCol });
+    }
+    return cellsToBeDrawn;
+}
+
+void MultiLevelHeaderView::paintSection(QPainter *painter, const QRect &rect, int logicalIdx) const
+{
+    const MultiLevelHeaderModel *m = static_cast<MultiLevelHeaderModel *>(this->model());
+    const int orient               = orientation();
+    const int levelCount           = (orient == Qt::Horizontal) ? m->rowCount() : m->columnCount();
+    std::set<Cell> cellsToBeDrawn  = getCellsToBeDrawn(this, m, orient, levelCount, logicalIdx);
+
+    for (const auto &cell : cellsToBeDrawn) {
+        QRect sectionRect = getCellRect(cell.row, cell.column);
+
+        // draw section with style
+        QStyleOptionHeader sectionStyle;
+        initStyleOption(&sectionStyle);
+        sectionStyle.textAlignment = Qt::AlignCenter;
+        sectionStyle.iconAlignment = Qt::AlignVCenter;
+        // sectionStyle.section = logicalIdx;
+        QModelIndex cellIndex = m->index(cell.row, cell.column);
+        sectionStyle.text     = cellIndex.data(Qt::DisplayRole).toString();
+        sectionStyle.rect     = sectionRect;
+
+        // file background or foreground color of the cell
+        QVariant bg = cellIndex.data(Qt::BackgroundRole);
+        QVariant fg = cellIndex.data(Qt::ForegroundRole);
+
+        if (bg.canConvert(QVariant::Brush)) {
+            sectionStyle.palette.setBrush(QPalette::Button, qvariant_cast<QBrush>(bg));
+            sectionStyle.palette.setBrush(QPalette::Window, qvariant_cast<QBrush>(bg));
+        }
+        if (fg.canConvert(QVariant::Brush)) {
+            sectionStyle.palette.setBrush(QPalette::ButtonText, qvariant_cast<QBrush>(fg));
+        }
+
+        painter->save();
+        qDrawShadePanel(painter, sectionStyle.rect, sectionStyle.palette, false, 1,
+                        &sectionStyle.palette.brush(QPalette::Button));
+        style()->drawControl(QStyle::CE_HeaderLabel, &sectionStyle, painter);
+        painter->restore();
+    }
+}
+
+QSize MultiLevelHeaderView::sectionSizeFromContents(int logicalIdx) const
+{
+    const MultiLevelHeaderModel *m = static_cast<const MultiLevelHeaderModel *>(this->model());
+    const int orient               = orientation();
+    const int levelCount           = (orient == Qt::Horizontal) ? m->rowCount() : m->columnCount();
+
+    int w = 0, h = 0;
+    if (orient == Qt::Horizontal) {
+        w = m->getColumnWidth(logicalIdx);
+        for (int i = 0; i < levelCount; ++i) {
+            h += m->getRowHeight(i);
+        }
+    } else {
+        for (int i = 0; i < levelCount; ++i) {
+            w += m->getColumnWidth(i);
+        }
+        h = m->getRowHeight(logicalIdx);
+    }
+    return QSize(w, h);
+}
+
+QModelIndex MultiLevelHeaderView::columnSpanIndex(const QModelIndex &currentIdx) const
+{
+    const MultiLevelHeaderModel *m = static_cast<MultiLevelHeaderModel *>(model());
+    const int curRow               = currentIdx.row();
+    const int curCol               = currentIdx.column();
+    int i                          = curCol;
+    while (i >= 0) {
+        QModelIndex spanIndex = m->index(curRow, i);
+        QVariant span         = spanIndex.data(COLUMN_SPAN_ROLE);
+        if (span.isValid() && spanIndex.column() + span.toInt() - 1 >= curCol)
+            return spanIndex;
+        i--;
+    }
+    return QModelIndex();
+}
+
+QModelIndex MultiLevelHeaderView::rowSpanIndex(const QModelIndex &currentIdx) const
+{
+    const MultiLevelHeaderModel *m = static_cast<MultiLevelHeaderModel *>(model());
+    const int curRow               = currentIdx.row();
+    const int curCol               = currentIdx.column();
+    int i                          = curRow;
+    while (i >= 0) {
+        QModelIndex spanIndex = m->index(i, curCol);
+        QVariant span         = spanIndex.data(ROW_SPAN_ROLE);
+        if (span.isValid() && spanIndex.row() + span.toInt() - 1 >= curRow)
+            return spanIndex;
+        i--;
+    }
+    return QModelIndex();
+}
+
+int MultiLevelHeaderView::columnSpanSize(int row, int from, int spanCount) const
+{
+    const MultiLevelHeaderModel *m = static_cast<MultiLevelHeaderModel *>(model());
+    int span                       = 0;
+    for (int i = from; i < from + spanCount; ++i) {
+        int colWidth = m->getColumnWidth(i);
+        span += colWidth;
+    }
+    return span;
+}
+
+int MultiLevelHeaderView::rowSpanSize(int column, int from, int spanCount) const
+{
+    const MultiLevelHeaderModel *m = static_cast<MultiLevelHeaderModel *>(model());
+    int span                       = 0;
+    for (int i = from; i < from + spanCount; ++i) {
+        int rowHeight = m->getRowHeight(i);
+        span += rowHeight;
+    }
+    return span;
+}
+
+bool MultiLevelHeaderView::getRootCell(int row, int column, int &rootCellRow, int &rootCellColumn) const
+{
+    const MultiLevelHeaderModel *m = static_cast<MultiLevelHeaderModel *>(this->model());
+    return m->getRootCell(row, column, rootCellRow, rootCellColumn);
+}
+
+QRect MultiLevelHeaderView::getCellRect(int row, int column) const
+{
+    const MultiLevelHeaderModel *m = static_cast<MultiLevelHeaderModel *>(this->model());
+    const int orient               = orientation();
+    QModelIndex cellIndex          = m->index(row, column);
+    auto colSpanVar                = cellIndex.data(COLUMN_SPAN_ROLE);
+    auto rowSpanVar                = cellIndex.data(ROW_SPAN_ROLE);
+    assert(colSpanVar.isValid() && rowSpanVar.isValid());
+    int colSpan = colSpanVar.toInt();
+    int rowSpan = rowSpanVar.toInt();
+    int w = 0, h = 0, l = 0, t = 0;
+    w = columnSpanSize(row, column, colSpan);
+    h = rowSpanSize(column, row, rowSpan);
+    if (orient == Qt::Horizontal) {
+        l = sectionViewportPosition(column);
+        for (int i = 0; i < row; ++i)
+            t += m->getRowHeight(i);
+    } else {
+        for (int i = 0; i < column; ++i)
+            l += m->getColumnWidth(i);
+        t = sectionViewportPosition(row);
+    }
+
+    QRect rect(l, t, w, h);
+    return rect;
+}
+
+/**
+ * @return section numbers
+ */
+int MultiLevelHeaderView::getSectionRange(QModelIndex &index, int *beginSection, int *endSection) const
+{
+    int row = index.row(), column = index.column();
+    int rootRow, rootCol;
+    bool success = getRootCell(row, column, rootRow, rootCol);
+    if (!success)
+        return 0;
+    const MultiLevelHeaderModel *m = static_cast<MultiLevelHeaderModel *>(model());
+    QModelIndex rootIndex          = m->index(rootRow, rootCol);
+    if (orientation() == Qt::Horizontal) {
+        int colSpanCnt = rootIndex.data(COLUMN_SPAN_ROLE).toInt();
+        *beginSection  = rootIndex.column();
+        ;
+        *endSection = *beginSection + colSpanCnt - 1;
+        index       = rootIndex;
+        return colSpanCnt;
+    } else {
+        int rowSpanCnt = rootIndex.data(ROW_SPAN_ROLE).toInt();
+        *beginSection  = rootIndex.row();
+        ;
+        *endSection = *beginSection + rowSpanCnt - 1;
+        index       = rootIndex;
+        return rowSpanCnt;
+    }
+}
+
+void MultiLevelHeaderView::onSectionResized(int logicalIndex, int oldSize, int newSize)
+{
+    MultiLevelHeaderModel *m = static_cast<MultiLevelHeaderModel *>(this->model());
+    const int orient         = orientation();
+    const int levelCount     = (orient == Qt::Horizontal) ? m->rowCount() : m->columnCount();
+    if (orient == Qt::Horizontal) {
+        m->setColumnWidth(logicalIndex, newSize);
+    } else {
+        m->setRowHeight(logicalIndex, newSize);
+    }
+
+    std::set<Cell> cellsToBeDrawn = getCellsToBeDrawn(this, m, orient, levelCount, logicalIndex);
+    for (const auto &cell : cellsToBeDrawn) {
+        QRect sectionRect = getCellRect(cell.row, cell.column);
+        if (orient == Qt::Horizontal) {
+            sectionRect.setWidth(viewport()->width() - sectionRect.left());
+        } else {
+            sectionRect.setHeight(viewport()->height() - sectionRect.top());
+        }
+        viewport()->update(sectionRect);
+    }
+}

+ 54 - 0
QFD/widgets/MultiLevelHeaderView.h

@@ -0,0 +1,54 @@
+#ifndef MULTILEVELHEADERVIEW_H
+#define MULTILEVELHEADERVIEW_H
+
+#include <QHeaderView>
+#include <QModelIndex>
+
+enum MyItemDataRole
+{
+    COLUMN_SPAN_ROLE = Qt::UserRole + 1,
+    ROW_SPAN_ROLE,
+};
+
+class MultiLevelHeaderView : public QHeaderView
+{
+    Q_OBJECT
+public:
+    MultiLevelHeaderView(Qt::Orientation orientation, int rows, int columns, QWidget *parent = 0);
+    virtual ~MultiLevelHeaderView();
+
+    void setRowHeight(int row, int rowHeight);
+    void setColumnWidth(int col, int colWidth);
+    void setCellData(int row, int column, int role, const QVariant &value);
+    // the below methods is just shortcut for setCellData
+    void setCellSpan(int row, int column, int rowSpanCount, int columnSpanCount);
+    void setCellBackgroundColor(int row, int column, const QColor &);
+    void setCellForegroundColor(int row, int column, const QColor &);
+    void setCellText(int row, int column, const QString &text);
+
+    QModelIndex columnSpanIndex(const QModelIndex &currentIndex) const;
+    QModelIndex rowSpanIndex(const QModelIndex &currentIndex) const;
+
+protected:
+    // override
+    virtual void mousePressEvent(QMouseEvent *event) override;
+    virtual QModelIndex indexAt(const QPoint &) const override;
+    virtual void paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const override;
+    virtual QSize sectionSizeFromContents(int logicalIndex) const override;
+
+    // inherent features
+    int columnSpanSize(int row, int from, int spanCount) const;
+    int rowSpanSize(int column, int from, int spanCount) const;
+    bool getRootCell(int row, int column, int &rootCellRow, int &rootCellColumn) const;
+    // (row, column) must be root cell of merged cells
+    QRect getCellRect(int row, int column) const;
+    int getSectionRange(QModelIndex &index, int *beginSection, int *endSection) const;
+
+protected slots:
+    void onSectionResized(int logicalIdx, int oldSize, int newSize);
+
+signals:
+    void sectionPressed(int from, int to);
+};
+
+#endif  // MULTILEVELHEADERVIEW_H