Skip to content

Commit deea13a

Browse files
committed
Plugin: add CCheckBoxHeader
1 parent ae93e2c commit deea13a

File tree

4 files changed

+324
-0
lines changed

4 files changed

+324
-0
lines changed

Src/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ list(APPEND PLUGIN_INSTALL_HEADER_FILES
5454
ParameterCompone/ParameterSSH.h
5555
ParameterCompone/ParameterSSHTunnelUI.h
5656
ParameterCompone/Stats.h
57+
CheckBoxHeader.h
5758
)
5859

5960
list(APPEND PLUGIN_HEADER_FILES
@@ -104,6 +105,7 @@ list(APPEND PLUGIN_SOURCE_FILES
104105
Hook.cpp
105106
Unix/DesktopShortcuts.cpp
106107
StatsAppUsage.cpp
108+
CheckBoxHeader.cpp
107109
)
108110

109111
set(PLUGIN_UI_FILES

Src/CheckBoxHeader.cpp

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
// Author: Kang Lin <kl222@126.com>
2+
3+
#include <QStandardItemModel>
4+
#include <QPainter>
5+
#include <QMouseEvent>
6+
#include <QStyleOptionButton>
7+
#include <QStyle>
8+
#include <QApplication>
9+
#include <QLoggingCategory>
10+
#include "CheckBoxHeader.h"
11+
12+
static Q_LOGGING_CATEGORY(log, "CCheckBoxHeader")
13+
CCheckBoxHeader::CCheckBoxHeader(Qt::Orientation orientation, QWidget *parent)
14+
: QHeaderView(orientation, parent)
15+
{
16+
// 可根据需要启用鼠标追踪等
17+
setSectionsClickable(true);
18+
}
19+
20+
void CCheckBoxHeader::SetCheckState(int index, Qt::CheckState state)
21+
{
22+
QStandardItemModel* pModel = qobject_cast<QStandardItemModel*>(this->model());
23+
if(pModel) {
24+
QStandardItem* item = nullptr;
25+
if(orientation() == Qt::Horizontal)
26+
item = pModel->horizontalHeaderItem(index);
27+
else
28+
item = pModel->verticalHeaderItem(index);
29+
if(item && item->isCheckable()) {
30+
item->setCheckState(state);
31+
viewport()->update();
32+
return;
33+
}
34+
}
35+
36+
if(!m_CheckableSections.contains(index)) {
37+
qCritical(log) << "Please call SetCheckable first to enable the checkbox";
38+
return;
39+
}
40+
if (m_CheckableSections[index] == state) return;
41+
m_CheckableSections[index] = state;
42+
viewport()->update();
43+
}
44+
45+
Qt::CheckState CCheckBoxHeader::GetCheckState(int index) const
46+
{
47+
QStandardItemModel* pModel = qobject_cast<QStandardItemModel*>(this->model());
48+
if(pModel) {
49+
QStandardItem* item = nullptr;
50+
if(orientation() == Qt::Horizontal)
51+
item = pModel->horizontalHeaderItem(index);
52+
else
53+
item = pModel->verticalHeaderItem(index);
54+
if(item && item->isCheckable()) {
55+
return item->checkState();
56+
}
57+
}
58+
59+
auto it = m_CheckableSections.find(index);
60+
if(m_CheckableSections.end() == it)
61+
return Qt::Unchecked;
62+
return *it;
63+
}
64+
65+
void CCheckBoxHeader::SetCheckable(int index, bool checkable, Qt::CheckState state)
66+
{
67+
QStandardItemModel* pModel = qobject_cast<QStandardItemModel*>(this->model());
68+
if(pModel) {
69+
QStandardItem* item = nullptr;
70+
if(orientation() == Qt::Horizontal)
71+
item = pModel->horizontalHeaderItem(index);
72+
else
73+
item = pModel->verticalHeaderItem(index);
74+
if(item) {
75+
item->setCheckable(checkable);
76+
item->setCheckState(state);
77+
return;
78+
}
79+
}
80+
81+
if (checkable)
82+
m_CheckableSections.insert(index, state);
83+
else
84+
m_CheckableSections.remove(index);
85+
viewport()->update();
86+
}
87+
88+
bool CCheckBoxHeader::isCheckable(int index) const
89+
{
90+
QStandardItemModel* pModel = qobject_cast<QStandardItemModel*>(this->model());
91+
if(pModel) {
92+
QStandardItem* item = nullptr;
93+
if(orientation() == Qt::Horizontal)
94+
item = pModel->horizontalHeaderItem(index);
95+
else
96+
item = pModel->verticalHeaderItem(index);
97+
return item && item->isCheckable();
98+
}
99+
100+
return m_CheckableSections.contains(index);
101+
}
102+
103+
QRect CCheckBoxHeader::CheckboxRect(const QRect &sectionRect) const
104+
{
105+
int size = style()->pixelMetric(QStyle::PM_IndicatorWidth, nullptr, this);
106+
if (size <= 0) size = 16;
107+
//qDebug(log) << "Size:" << size;
108+
int x = 0;
109+
int y = 0;
110+
if(orientation() == Qt::Horizontal) {
111+
x = sectionRect.left() + 4;
112+
y = sectionRect.center().y() - size / 2;
113+
} else {
114+
x = sectionRect.center().x() - size / 2;
115+
y = sectionRect.bottom() - 4;
116+
}
117+
return QRect(x, y, size, size);
118+
}
119+
120+
void CCheckBoxHeader::paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const
121+
{
122+
if (!isCheckable(logicalIndex) || isSectionHidden(logicalIndex)) {
123+
QHeaderView::paintSection(painter, rect, logicalIndex);
124+
return;
125+
}
126+
127+
// 1) 绘制默认背景
128+
painter->save();
129+
QStyleOptionHeader option;
130+
option.initFrom(this);
131+
option.rect = rect;
132+
option.section = logicalIndex;
133+
option.orientation = this->orientation();
134+
style()->drawControl(QStyle::CE_Header, &option, painter, this);
135+
painter->restore();
136+
137+
// 2) 如果该列可显示复选框,绘制复选框
138+
QRect cbRect = CheckboxRect(rect);
139+
DrawCheckBox(painter, cbRect, GetCheckState(logicalIndex), true);
140+
141+
// 3) 绘制文本(在复选框右侧留出间距)
142+
QString text;
143+
if (model()) {
144+
QVariant v = model()->headerData(logicalIndex, Qt::Horizontal, Qt::DisplayRole);
145+
text = v.isValid() ? v.toString() : QString();
146+
}
147+
if (!text.isEmpty()) {
148+
// 调整文本区域(避开复选框)
149+
int textLeft = cbRect.right() + 4; // 复选框右边留4像素间距
150+
QRect textRect(textLeft, rect.top(), rect.width() - (textLeft - rect.left()), rect.height());
151+
// 绘制文本
152+
QVariant vAlign = model() ? model()->headerData(
153+
logicalIndex, orientation(),
154+
Qt::TextAlignmentRole)
155+
: Qt::AlignLeft;
156+
Qt::Alignment alignment = Qt::AlignCenter;
157+
if(vAlign.isValid())
158+
alignment = (Qt::Alignment)vAlign.toInt();
159+
this->DrawText(painter, textRect, text, alignment);
160+
}
161+
162+
return;
163+
}
164+
165+
void CCheckBoxHeader::DrawCheckBox(QPainter* painter, const QRect& rect,
166+
Qt::CheckState state, bool enabled) const
167+
{
168+
QStyleOptionButton option;
169+
option.rect = rect;
170+
option.state = QStyle::State_Enabled | QStyle::State_Active;
171+
172+
if (enabled) {
173+
option.state |= QStyle::State_Enabled;
174+
}
175+
176+
switch (state) {
177+
case Qt::Checked:
178+
option.state |= QStyle::State_On;
179+
break;
180+
case Qt::PartiallyChecked:
181+
option.state |= QStyle::State_NoChange;
182+
break;
183+
case Qt::Unchecked:
184+
option.state |= QStyle::State_Off;
185+
break;
186+
}
187+
188+
// 绘制复选框
189+
style()->drawControl(QStyle::CE_CheckBox, &option, painter, this);
190+
}
191+
192+
void CCheckBoxHeader::DrawText(QPainter* painter, const QRect& rect,
193+
const QString& text, Qt::Alignment alignment) const
194+
{
195+
if (text.isEmpty()) return;
196+
197+
painter->save();
198+
199+
// 文本省略处理
200+
QFontMetrics fm = painter->fontMetrics();
201+
QString elided = fm.elidedText(text, Qt::ElideRight, rect.width());
202+
203+
QPalette palette = this->palette();
204+
painter->setPen(palette.color(QPalette::Text));
205+
206+
// 绘制文本
207+
painter->drawText(rect, alignment, elided);
208+
209+
painter->restore();
210+
}
211+
212+
void CCheckBoxHeader::mousePressEvent(QMouseEvent *event)
213+
{
214+
int logical = logicalIndexAt(event->pos());
215+
if (logical >= 0 && isCheckable(logical)) {
216+
// 使用 sectionViewportPosition + sectionSize 构造节矩形,避免兼容性问题
217+
int x = sectionViewportPosition(logical);
218+
int w = sectionSize(logical);
219+
QRect sectionRect;
220+
if (orientation() == Qt::Horizontal) {
221+
sectionRect = QRect(x, 0, w, height());
222+
} else {
223+
sectionRect = QRect(0, x, width(), w);
224+
}
225+
if (CheckboxRect(sectionRect).contains(event->pos())) {
226+
Qt::CheckState state = GetCheckState(logical);
227+
switch (state) {
228+
case Qt::Unchecked:
229+
state = Qt::PartiallyChecked;
230+
break;
231+
case Qt::PartiallyChecked:
232+
state = Qt::Checked;
233+
break;
234+
case Qt::Checked:
235+
state = Qt::Unchecked;
236+
break;
237+
}
238+
SetCheckState(logical, state);
239+
emit sigCheckStateChanged(logical, state);
240+
// 结束事件以避免触发排序(如不需要可调整)
241+
//event->accept();
242+
//return;
243+
}
244+
}
245+
246+
QHeaderView::mousePressEvent(event);
247+
}

Src/CheckBoxHeader.h

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Author: Kang Lin <kl222@126.com>
2+
3+
#pragma once
4+
#include <QHeaderView>
5+
#include <QSet>
6+
7+
/*!
8+
* \~chinese 包含复选框的表头
9+
* \details
10+
* - 如果模型是 QStandardItemModel ,则会根据 QStandardItem::isCheckable() 来判断是否显示复选框
11+
* - 如果模型不是 QStandardItemModel ,则使用 SetCheckState() 来决定是否显示复选框
12+
*
13+
* \~english Check box header view
14+
* \details
15+
* - If the model is a QStandardItemModel, whether to show a checkbox is determined by QStandardItem::isCheckable()
16+
* - If the model is not a QStandardItemModel, SetCheckState() is used to decide whether to show a checkbox
17+
*/
18+
class CCheckBoxHeader : public QHeaderView {
19+
Q_OBJECT
20+
21+
public:
22+
explicit CCheckBoxHeader(Qt::Orientation orientation, QWidget *parent = nullptr);
23+
24+
/*!
25+
* \~chinese 设置是否显示复选框
26+
* \note 在调用前必须先设置了模型
27+
*/
28+
void SetCheckable(int index, bool checkable, Qt::CheckState state = Qt::Unchecked);
29+
bool isCheckable(int index) const;
30+
/*!
31+
* \~chinese 设置表头复选框状态
32+
* \note 在调用前必须先设置了模型
33+
*/
34+
void SetCheckState(int index, Qt::CheckState state);
35+
Qt::CheckState GetCheckState(int index) const;
36+
37+
signals:
38+
void sigCheckStateChanged(int index, Qt::CheckState state);
39+
40+
protected:
41+
void paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const override;
42+
void mousePressEvent(QMouseEvent *event) override;
43+
44+
private:
45+
QRect CheckboxRect(const QRect &sectionRect) const;
46+
void DrawText(QPainter* painter, const QRect& rect,
47+
const QString& text, Qt::Alignment alignment) const;
48+
void DrawCheckBox(QPainter* painter, const QRect& rect,
49+
Qt::CheckState state, bool enabled) const;
50+
51+
QHash<int, Qt::CheckState> m_CheckableSections;
52+
};

Src/FrmManagePlugins.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "Plugin.h"
99
#include "RabbitCommonDir.h"
1010
#include "FrmManagePlugins.h"
11+
#include "CheckBoxHeader.h"
1112
#include "ui_FrmManagePlugins.h"
1213

1314
static Q_LOGGING_CATEGORY(log, "Manage")
@@ -238,5 +239,27 @@ int CFrmManagePlugins::SetFilterHeader()
238239
m_pModelFilter->setHorizontalHeaderItem(ColumnNo::Name, new QStandardItem(tr("Name")));
239240
m_pModelFilter->setHorizontalHeaderItem(ColumnNo::Type, new QStandardItem(tr("Type")));
240241
m_pModelFilter->setHorizontalHeaderItem(ColumnNo::Path, new QStandardItem(tr("Path")));
242+
auto pHeader = new CCheckBoxHeader(Qt::Horizontal, ui->tvFilter);
243+
if(pHeader) {
244+
ui->tvFilter->setHorizontalHeader(pHeader);
245+
pHeader->SetCheckable(ColumnNo::Blacklist, true);
246+
pHeader->SetCheckable(ColumnNo::Whitelist, true);
247+
bool check = false;
248+
check = connect(pHeader, &CCheckBoxHeader::sigCheckStateChanged,
249+
this, [this](int index, Qt::CheckState state){
250+
for(int row = 0; row < m_pModelFilter->rowCount(); row++) {
251+
auto item = m_pModelFilter->item(row, index);
252+
switch(state)
253+
{
254+
case Qt::PartiallyChecked:
255+
break;
256+
default:
257+
item->setCheckState(state);
258+
break;
259+
}
260+
}
261+
});
262+
Q_ASSERT(check);
263+
}
241264
return 0;
242265
}

0 commit comments

Comments
 (0)