JS8Call-Improved master
Loading...
Searching...
No Matches
SemiSortableHeader.h
1#ifndef JS8CALL_SEMISORTABLEHEADER_H
2#define JS8CALL_SEMISORTABLEHEADER_H
3
4#include <QHeaderView>
5#include <QIcon>
6#include <QMouseEvent>
7#include <QPainter>
8#include <QPointer>
9#include <QSet>
10#include <QStyleOptionHeader>
11#include <QTableWidget>
12#include <QVariant>
13
14//
15// Class SemiSortableHeader
16//
17// QHeaderView derivative that allow selective disabling of sorting on specified columns.
18//
19class SemiSortableHeader : public QHeaderView {
20
21public:
22 explicit SemiSortableHeader(Qt::Orientation ori, QWidget *parent = nullptr)
23 : QHeaderView(ori, parent) {
24 setSectionsClickable(true);
25 setSortIndicatorShown(true);
26 setSortIndicatorClearable(false);
27 setMouseTracking(true);
28 }
29
30 void attachTo(QTableWidget *table) {
31 table_ = table;
32 table_->setHorizontalHeader(this);
33
34 // Keep sorting enabled so the style draws the indicator
35 table_->setSortingEnabled(true);
36
37 // Initialize to a sensible default: first sortable column, descending
38 sortCol_ = firstSortableSection();
39 sortOrder_ = Qt::DescendingOrder;
40
41 if (sortCol_ >= 0) {
42 QSignalBlocker b(this);
43 setSortIndicator(sortCol_, sortOrder_);
44 }
45
46 connect(this, &QHeaderView::sectionClicked, this,
47 &SemiSortableHeader::onSectionClicked, Qt::UniqueConnection);
48 }
49
50 // New multi-column API
51 void setNonSortableColumns(const QSet<int> &cols) {
52 nonSortable_ = cols;
53 normalizeSortIfNeeded();
54 viewport()->update();
55 }
56
57 void addNonSortableColumn(int col) {
58 if (col >= 0) {
59 nonSortable_.insert(col);
60 normalizeSortIfNeeded();
61 viewport()->update();
62 }
63 }
64
65 void removeNonSortableColumn(int col) {
66 nonSortable_.remove(col);
67 normalizeSortIfNeeded();
68 viewport()->update();
69 }
70
71 bool isSortableColumn(int col) const {
72 return col >= 0 && !nonSortable_.contains(col);
73 }
74
75private slots:
76 void onSectionClicked(int column) {
77 if (!table_)
78 return;
79 if (!isSortableColumn(column))
80 return;
81
82 if (column == sortCol_) {
83 sortOrder_ = (sortOrder_ == Qt::AscendingOrder) ? Qt::DescendingOrder
84 : Qt::AscendingOrder;
85 } else {
86 sortCol_ = column;
87 sortOrder_ = Qt::AscendingOrder;
88 }
89
90 // Update indicator without re-triggering click logic
91 {
92 QSignalBlocker b(this);
93 setSortIndicator(sortCol_, sortOrder_);
94 }
95
96 // Prevent Qt's built-in sorting from running while we sort
97 table_->setSortingEnabled(false);
98 table_->sortItems(sortCol_, sortOrder_);
99 table_->setSortingEnabled(true);
100 }
101
102protected:
103 void paintSection(QPainter *painter, const QRect &rect,
104 int logicalIndex) const override {
105 // Normal columns: keep Qt default painting
106 if (isSortableColumn(logicalIndex)) {
107 QHeaderView::paintSection(painter, rect, logicalIndex);
108 return;
109 }
110
111 // Dead columns: custom paint (no hover/pressed, no sort arrow), but keep
112 // label
113 QStyleOptionHeader opt;
114 initStyleOption(&opt);
115
116 opt.rect = rect;
117 opt.section = logicalIndex;
118
119 if (auto *m = model()) {
120 opt.text = m->headerData(logicalIndex, orientation(), Qt::DisplayRole)
121 .toString();
122
123 const QVariant align =
124 m->headerData(logicalIndex, orientation(), Qt::TextAlignmentRole);
125 if (align.isValid())
126 opt.textAlignment = Qt::Alignment(align.toInt());
127
128 const QVariant deco =
129 m->headerData(logicalIndex, orientation(), Qt::DecorationRole);
130 if (deco.isValid())
131 opt.icon = deco.value<QIcon>();
132 }
133
134 // Section position affects borders/separators
135 const int v = visualIndex(logicalIndex);
136 const int n = count();
137 if (n <= 1)
138 opt.position = QStyleOptionHeader::OnlyOneSection;
139 else if (v == 0)
140 opt.position = QStyleOptionHeader::Beginning;
141 else if (v == n - 1)
142 opt.position = QStyleOptionHeader::End;
143 else
144 opt.position = QStyleOptionHeader::Middle;
145
146 opt.state &= ~QStyle::State_MouseOver;
147 opt.state &= ~QStyle::State_Sunken;
148
149 // Ensure no sort arrow is painted on dead columns
150 opt.sortIndicator = QStyleOptionHeader::None;
151
152 style()->drawControl(QStyle::CE_Header, &opt, painter, this);
153 }
154
155 void mousePressEvent(QMouseEvent *e) override {
156 const int col = logicalIndexAt(e->pos());
157 if (!isSortableColumn(col)) {
158 e->accept(); // swallow: no indicator change, no click signal
159 return;
160 }
161 QHeaderView::mousePressEvent(e);
162 }
163
164private:
165 int firstSortableSection() const {
166 for (int logical = 0; logical < count(); ++logical) {
167 if (isSortableColumn(logical))
168 return logical;
169 }
170 return -1; // none sortable
171 }
172
173 void normalizeSortIfNeeded() {
174 // If current sort column became non-sortable, move to first sortable
175 if (sortCol_ >= 0 && !isSortableColumn(sortCol_)) {
176 sortCol_ = firstSortableSection();
177 sortOrder_ = Qt::DescendingOrder;
178 if (sortCol_ >= 0) {
179 QSignalBlocker b(this);
180 setSortIndicator(sortCol_, sortOrder_);
181 } else {
182 QSignalBlocker b(this);
183 setSortIndicator(-1, Qt::AscendingOrder); // clear indicator
184 }
185 }
186 }
187
188private:
189 QPointer<QTableWidget> table_;
190 QSet<int> nonSortable_;
191
192 int sortCol_ = -1;
193 Qt::SortOrder sortOrder_ = Qt::AscendingOrder;
194};
195
196#endif // JS8CALL_SEMISORTABLEHEADER_H