#include "MultiLevelHeaderView.h" #include #include #include #include #include #include #include #include /** * * 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 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 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 m_rowSizes; std::vector m_columnSizes; QMap> m_data; QMap 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() << 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() << 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(model()); if (m) delete m; setModel(nullptr); } void MultiLevelHeaderView::setRowHeight(int row, int rowHeight) { MultiLevelHeaderModel *m = static_cast(model()); m->setRowHeight(row, rowHeight); if (orientation() == Qt::Vertical) resizeSection(row, rowHeight); } void MultiLevelHeaderView::setColumnWidth(int col, int colWidth) { MultiLevelHeaderModel *m = static_cast(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(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(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(model()); m->setData(m->index(row, column), color, Qt::BackgroundRole); } void MultiLevelHeaderView::setCellForegroundColor(int row, int column, const QColor &color) { MultiLevelHeaderModel *m = static_cast(model()); m->setData(m->index(row, column), color, Qt::ForegroundRole); } void MultiLevelHeaderView::setCellText(int row, int column, const QString &text) { MultiLevelHeaderModel *m = static_cast(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(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(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 getCellsToBeDrawn(const MultiLevelHeaderView *view, const MultiLevelHeaderModel *m, int orient, int levelCount, int logicalIdx) { std::set 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(this->model()); const int orient = orientation(); const int levelCount = (orient == Qt::Horizontal) ? m->rowCount() : m->columnCount(); std::set 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(§ionStyle); 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(bg)); sectionStyle.palette.setBrush(QPalette::Window, qvariant_cast(bg)); } if (fg.canConvert(QVariant::Brush)) { sectionStyle.palette.setBrush(QPalette::ButtonText, qvariant_cast(fg)); } painter->save(); qDrawShadePanel(painter, sectionStyle.rect, sectionStyle.palette, false, 1, §ionStyle.palette.brush(QPalette::Button)); style()->drawControl(QStyle::CE_HeaderLabel, §ionStyle, painter); painter->restore(); } } QSize MultiLevelHeaderView::sectionSizeFromContents(int logicalIdx) const { const MultiLevelHeaderModel *m = static_cast(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 ¤tIdx) const { const MultiLevelHeaderModel *m = static_cast(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 ¤tIdx) const { const MultiLevelHeaderModel *m = static_cast(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(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(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(this->model()); return m->getRootCell(row, column, rootCellRow, rootCellColumn); } QRect MultiLevelHeaderView::getCellRect(int row, int column) const { const MultiLevelHeaderModel *m = static_cast(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(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(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 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); } }