Skip to content

Commit 62782a4

Browse files
committed
Add TableViewBase.h and base class QTableView uses in stringsview, memory map, searches, and tag types
1 parent 2303f75 commit 62782a4

File tree

5 files changed

+182
-6
lines changed

5 files changed

+182
-6
lines changed

ui/memorymap.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "uitypes.h"
1818
#include "fontsettings.h"
1919
#include "viewframe.h"
20+
#include "tableviewbase.h"
2021

2122
/*!
2223
@@ -172,14 +173,13 @@ class BINARYNINJAUIAPI SegmentWidget : public QWidget
172173
Q_OBJECT
173174

174175
BinaryViewRef m_data;
175-
QTableView* m_table;
176+
TableViewBase* m_table;
176177
SegmentModel* m_model;
177178
QSortFilterProxyModel* m_proxyModel;
178179
std::mutex m_updateMutex;
179180

180181
//void updateInfo();
181182
void showContextMenu(const QPoint& point);
182-
QMenu* createHeaderContextMenu(const QPoint& p);
183183
void restoreDefaults();
184184

185185
void addMemoryRegion(SegmentRef segment);
@@ -251,7 +251,7 @@ class BINARYNINJAUIAPI SectionWidget : public QWidget
251251
Q_OBJECT
252252

253253
BinaryViewRef m_data;
254-
QTableView* m_table;
254+
TableViewBase* m_table;
255255
SectionModel* m_model;
256256
QSortFilterProxyModel* m_proxyModel;
257257
std::mutex m_updateMutex;

ui/searchresult.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#pragma once
22

3+
#include <tableviewbase.h>
34
#include <QtCore/QAbstractItemModel>
45
#include <QtCore/QItemSelectionModel>
56
#include <QtCore/QSortFilterProxyModel>
@@ -173,7 +174,7 @@ class SearchResultWidget;
173174
/*!
174175
\ingroup searchresult
175176
*/
176-
class BINARYNINJAUIAPI SearchResultTable : public QTableView
177+
class BINARYNINJAUIAPI SearchResultTable : public TableViewBase
177178
{
178179
Q_OBJECT
179180

ui/stringsview.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "render.h"
1010
#include "filter.h"
1111
#include "uicontext.h"
12+
#include "tableviewbase.h"
1213

1314
#define STRINGS_LIST_UPDATE_INTERVAL 250
1415

@@ -133,7 +134,7 @@ class StringsViewSidebarWidget;
133134
134135
\ingroup stringsview
135136
*/
136-
class BINARYNINJAUIAPI StringsView : public QTableView, public View, public FilterTarget
137+
class BINARYNINJAUIAPI StringsView : public TableViewBase, public View, public FilterTarget
137138
{
138139
Q_OBJECT
139140

@@ -148,6 +149,9 @@ class BINARYNINJAUIAPI StringsView : public QTableView, public View, public Filt
148149
uint64_t m_selectionBegin, m_selectionEnd;
149150
uint64_t m_currentlySelectedDataAddress;
150151

152+
QPointer<QHeaderView> m_horizontalHeader;
153+
QPointer<QHeaderView> m_verticalHeader;
154+
151155
public:
152156
StringsView(BinaryViewRef data, StringsContainer* container);
153157

@@ -190,6 +194,7 @@ class BINARYNINJAUIAPI StringsView : public QTableView, public View, public Filt
190194
virtual void mousePressEvent(QMouseEvent* event) override;
191195
virtual void paintEvent(QPaintEvent* event) override;
192196
virtual bool event(QEvent* event) override;
197+
int defaultSectionWidth(int logicalIndex, int charWidth) const override;
193198

194199
private Q_SLOTS:
195200
void goToString(const QModelIndex& idx);

ui/tableviewbase.h

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
//
2+
// Created by Alexander Khosrowshahi on 8/22/25.
3+
//
4+
5+
#pragma once
6+
7+
#include <QtWidgets/QTableView>
8+
#include <QtWidgets/QHeaderView>
9+
#include <QtCore/QTimer>
10+
#include <QMenu>
11+
12+
/// Base class for table views in Binary Ninja views
13+
/// - Moveable, resizeable columns with saved state
14+
/// - QSettings save to Tables/<viewName>/<Suffix>
15+
/// - Reset columns context menu action
16+
class TableViewBase: public QTableView {
17+
Q_OBJECT
18+
19+
public:
20+
explicit TableViewBase(QWidget* parent = nullptr, const QString& viewName = {}): QTableView(parent), m_viewName(viewName) {
21+
auto* hh = horizontalHeader();
22+
hh->setStretchLastSection(true);
23+
hh->setSectionResizeMode(QHeaderView::Interactive);
24+
hh->setSectionsMovable(true);
25+
hh->setSectionsClickable(true);
26+
hh->setSortIndicatorShown(true);
27+
hh->setSortIndicator(0, Qt::AscendingOrder);
28+
hh->setContextMenuPolicy(Qt::CustomContextMenu);
29+
connect(hh, &QHeaderView::customContextMenuRequested, this,
30+
[this, hh](const QPoint& p) {
31+
QMenu menu(hh);
32+
QAction* reset = menu.addAction(tr("Reset Column Layout"));
33+
connect(reset, &QAction::triggered, this, &TableViewBase::resetColumnLayout);
34+
populateHeaderContextMenu(&menu, p);
35+
menu.exec(hh->viewport()->mapToGlobal(p));
36+
});
37+
38+
m_headerSaveDebounce.setSingleShot(true);
39+
m_headerSaveDebounce.setInterval(150);
40+
41+
connect(&m_headerSaveDebounce, &QTimer::timeout, this, &TableViewBase::saveHeaderState);
42+
connect(hh, &QHeaderView::sectionResized, this, &TableViewBase::scheduleSaveHeaderState);
43+
connect(hh, &QHeaderView::sectionMoved, this, &TableViewBase::scheduleSaveHeaderState);
44+
45+
setShowGrid(false);
46+
setSortingEnabled(true);
47+
48+
QMetaObject::invokeMethod(this, "restoreHeaderState", Qt::QueuedConnection);
49+
}
50+
51+
52+
void setModel(QAbstractItemModel* m) override
53+
{
54+
QTableView::setModel(m);
55+
if (!m) return;
56+
connect(m, &QAbstractItemModel::modelReset, this, &TableViewBase::restoreHeaderState);
57+
connect(m, &QAbstractItemModel::columnsInserted, this, &TableViewBase::restoreHeaderState);
58+
connect(m, &QAbstractItemModel::columnsRemoved, this, &TableViewBase::restoreHeaderState);
59+
60+
// Grab default header state
61+
QTimer::singleShot(0, this, [this]{
62+
captureDefaultHeaderState();
63+
});
64+
65+
QMetaObject::invokeMethod(this, "restoreHeaderState", Qt::QueuedConnection);
66+
}
67+
68+
// Save after debounce for repeated move/drag
69+
void scheduleSaveHeaderState() { m_headerSaveDebounce.start(); }
70+
71+
Q_SIGNALS:
72+
// For owners/derived classes to add their own menu items
73+
void populateHeaderContextMenu(QMenu*, const QPoint&);
74+
75+
protected:
76+
QString viewName() const {
77+
if (!m_viewName.isEmpty()) return m_viewName;
78+
if (!objectName().isEmpty()) return objectName();
79+
return metaObject()->className();
80+
}
81+
82+
QString settingsKey(const QString& suffix) const {
83+
return QStringLiteral("tables/%1/%2").arg(viewName(), suffix);
84+
}
85+
86+
void saveHeaderState() const {
87+
auto* hh = horizontalHeader();
88+
if (!hh) return;
89+
QSettings s;
90+
s.setValue(settingsKey("horizontalHeaderState"), hh->saveState());
91+
}
92+
93+
void restoreHeaderState() const
94+
{
95+
auto* hh = horizontalHeader();
96+
if (!hh) return;
97+
QSettings s;
98+
const QByteArray st = s.value(settingsKey("horizontalHeaderState")).toByteArray();
99+
if (!st.isEmpty()) hh->restoreState(st);
100+
101+
const QByteArray def = s.value(settingsKey("horizontalHeaderDefaultState")).toByteArray();
102+
if (!def.isEmpty()) hh->restoreState(def);
103+
}
104+
105+
virtual int defaultSectionWidth(const int logicalIndex, const int charWidth) const
106+
{
107+
QString headerText;
108+
if (model()) {
109+
headerText = model()->headerData(logicalIndex, Qt::Horizontal, Qt::DisplayRole).toString();
110+
}
111+
constexpr int minChars = 8;
112+
const int headerChars = qMax(minChars, headerText.size() + 2);
113+
return headerChars * charWidth;
114+
}
115+
116+
/// Grabs default header states on startup to save
117+
/// Kind of a hacky fix, but many of our tables have manually set widths,
118+
/// so compensating for them is a hassle.
119+
void captureDefaultHeaderState() const
120+
{
121+
auto* hh = horizontalHeader();
122+
if (!hh) return;
123+
124+
QSettings s;
125+
const auto key = settingsKey("horizontalHeaderDefaultState");
126+
if (!s.contains(key)) {
127+
s.setValue(key, hh->saveState());
128+
s.sync();
129+
}
130+
}
131+
132+
133+
void resetColumnLayout() const
134+
{
135+
auto* hh = horizontalHeader();
136+
if (!hh || !model()) return;
137+
138+
{
139+
QSettings s;
140+
s.remove(settingsKey("horizontalHeaderState"));
141+
}
142+
143+
QSettings s;
144+
const QByteArray def = s.value(settingsKey("horizontalHeaderDefaultState")).
145+
toByteArray();
146+
if (!def.isEmpty() && hh->restoreState(def))
147+
{
148+
return;
149+
}
150+
151+
// If no default or set user layout, use size hints
152+
for (int c = 0; c < model()->columnCount(); ++c)
153+
{
154+
constexpr int extra = 12;
155+
int w = sizeHintForColumn(c);
156+
QVariant head = model()->headerData(c, Qt::Horizontal, Qt::SizeHintRole);
157+
int headerW = hh->sectionSizeHint(c);
158+
if (head.canConvert<QSize>())
159+
headerW = std::max(headerW, head.toSize().width());
160+
hh->resizeSection(c, std::max(w, headerW) + extra);
161+
}
162+
}
163+
164+
165+
private:
166+
QString m_viewName;
167+
QTimer m_headerSaveDebounce;
168+
169+
};

ui/tagtypelist.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <QtWidgets/QComboBox>
99
#include "binaryninjaapi.h"
1010
#include "viewframe.h"
11+
#include "tableviewbase.h"
1112

1213
#define TAGS_UPDATE_CHECK_INTERVAL 200
1314

@@ -88,7 +89,7 @@ class BINARYNINJAUIAPI TagTypeItemDelegate : public QItemDelegate
8889
8990
\ingroup tagtypelist
9091
*/
91-
class BINARYNINJAUIAPI TagTypeList : public QTableView, public BinaryNinja::BinaryDataNotification
92+
class BINARYNINJAUIAPI TagTypeList : public TableViewBase, public BinaryNinja::BinaryDataNotification
9293
{
9394
Q_OBJECT
9495

0 commit comments

Comments
 (0)