summaryrefslogtreecommitdiff
path: root/ui
diff options
context:
space:
mode:
authorAlexander Khosrowshahi <alexk@vector35.com>2025-08-28 16:03:23 -0400
committerJordan <github@psifertex.com>2025-10-24 11:48:53 -0400
commit4e3ef2ce073efe0cf1c7b744e720202afe7e294e (patch)
tree9ef816922564b6a5d917ca8f8b8cb9038526d57e /ui
parent8ffd2aa208552bcfef8d5436b62826d17f95ae86 (diff)
Add TableViewBase.h and base class QTableView uses in stringsview, memory map, searches, and tag types
Diffstat (limited to 'ui')
-rw-r--r--ui/memorymap.h6
-rw-r--r--ui/searchresult.h3
-rw-r--r--ui/stringsview.h7
-rw-r--r--ui/tableviewbase.h169
-rw-r--r--ui/tagtypelist.h3
5 files changed, 182 insertions, 6 deletions
diff --git a/ui/memorymap.h b/ui/memorymap.h
index 44bd0195..34753b40 100644
--- a/ui/memorymap.h
+++ b/ui/memorymap.h
@@ -17,6 +17,7 @@
#include "uitypes.h"
#include "fontsettings.h"
#include "viewframe.h"
+#include "tableviewbase.h"
/*!
@@ -172,14 +173,13 @@ class BINARYNINJAUIAPI SegmentWidget : public QWidget
Q_OBJECT
BinaryViewRef m_data;
- QTableView* m_table;
+ TableViewBase* m_table;
SegmentModel* m_model;
QSortFilterProxyModel* m_proxyModel;
std::mutex m_updateMutex;
//void updateInfo();
void showContextMenu(const QPoint& point);
- QMenu* createHeaderContextMenu(const QPoint& p);
void restoreDefaults();
void addMemoryRegion(SegmentRef segment);
@@ -251,7 +251,7 @@ class BINARYNINJAUIAPI SectionWidget : public QWidget
Q_OBJECT
BinaryViewRef m_data;
- QTableView* m_table;
+ TableViewBase* m_table;
SectionModel* m_model;
QSortFilterProxyModel* m_proxyModel;
std::mutex m_updateMutex;
diff --git a/ui/searchresult.h b/ui/searchresult.h
index 9fe28996..48c67de1 100644
--- a/ui/searchresult.h
+++ b/ui/searchresult.h
@@ -1,5 +1,6 @@
#pragma once
+#include <tableviewbase.h>
#include <QtCore/QAbstractItemModel>
#include <QtCore/QItemSelectionModel>
#include <QtCore/QSortFilterProxyModel>
@@ -173,7 +174,7 @@ class SearchResultWidget;
/*!
\ingroup searchresult
*/
-class BINARYNINJAUIAPI SearchResultTable : public QTableView
+class BINARYNINJAUIAPI SearchResultTable : public TableViewBase
{
Q_OBJECT
diff --git a/ui/stringsview.h b/ui/stringsview.h
index b8689a3a..7be62922 100644
--- a/ui/stringsview.h
+++ b/ui/stringsview.h
@@ -9,6 +9,7 @@
#include "render.h"
#include "filter.h"
#include "uicontext.h"
+#include "tableviewbase.h"
#define STRINGS_LIST_UPDATE_INTERVAL 250
@@ -166,7 +167,7 @@ class StringsViewSidebarWidget;
\ingroup stringsview
*/
-class BINARYNINJAUIAPI StringsView : public QTableView, public View, public FilterTarget
+class BINARYNINJAUIAPI StringsView : public TableViewBase, public View, public FilterTarget
{
Q_OBJECT
@@ -183,6 +184,9 @@ class BINARYNINJAUIAPI StringsView : public QTableView, public View, public Filt
uint64_t m_currentlySelectedDataAddress;
std::optional<BinaryNinja::DerivedString> m_derivedString;
+ QPointer<QHeaderView> m_horizontalHeader;
+ QPointer<QHeaderView> m_verticalHeader;
+
public:
StringsView(BinaryViewRef data, StringsContainer* container);
@@ -226,6 +230,7 @@ class BINARYNINJAUIAPI StringsView : public QTableView, public View, public Filt
virtual void mousePressEvent(QMouseEvent* event) override;
virtual void paintEvent(QPaintEvent* event) override;
virtual bool event(QEvent* event) override;
+ int defaultSectionWidth(int logicalIndex, int charWidth) const override;
private Q_SLOTS:
void goToString(const QModelIndex& idx);
diff --git a/ui/tableviewbase.h b/ui/tableviewbase.h
new file mode 100644
index 00000000..6a36505c
--- /dev/null
+++ b/ui/tableviewbase.h
@@ -0,0 +1,169 @@
+//
+// Created by Alexander Khosrowshahi on 8/22/25.
+//
+
+#pragma once
+
+#include <QtWidgets/QTableView>
+#include <QtWidgets/QHeaderView>
+#include <QtCore/QTimer>
+#include <QMenu>
+
+/// Base class for table views in Binary Ninja views
+/// - Moveable, resizeable columns with saved state
+/// - QSettings save to Tables/<viewName>/<Suffix>
+/// - Reset columns context menu action
+class TableViewBase: public QTableView {
+ Q_OBJECT
+
+public:
+ explicit TableViewBase(QWidget* parent = nullptr, const QString& viewName = {}): QTableView(parent), m_viewName(viewName) {
+ auto* hh = horizontalHeader();
+ hh->setStretchLastSection(true);
+ hh->setSectionResizeMode(QHeaderView::Interactive);
+ hh->setSectionsMovable(true);
+ hh->setSectionsClickable(true);
+ hh->setSortIndicatorShown(true);
+ hh->setSortIndicator(0, Qt::AscendingOrder);
+ hh->setContextMenuPolicy(Qt::CustomContextMenu);
+ connect(hh, &QHeaderView::customContextMenuRequested, this,
+ [this, hh](const QPoint& p) {
+ QMenu menu(hh);
+ QAction* reset = menu.addAction(tr("Reset Column Layout"));
+ connect(reset, &QAction::triggered, this, &TableViewBase::resetColumnLayout);
+ populateHeaderContextMenu(&menu, p);
+ menu.exec(hh->viewport()->mapToGlobal(p));
+ });
+
+ m_headerSaveDebounce.setSingleShot(true);
+ m_headerSaveDebounce.setInterval(150);
+
+ connect(&m_headerSaveDebounce, &QTimer::timeout, this, &TableViewBase::saveHeaderState);
+ connect(hh, &QHeaderView::sectionResized, this, &TableViewBase::scheduleSaveHeaderState);
+ connect(hh, &QHeaderView::sectionMoved, this, &TableViewBase::scheduleSaveHeaderState);
+
+ setShowGrid(false);
+ setSortingEnabled(true);
+
+ QMetaObject::invokeMethod(this, "restoreHeaderState", Qt::QueuedConnection);
+ }
+
+
+ void setModel(QAbstractItemModel* m) override
+ {
+ QTableView::setModel(m);
+ if (!m) return;
+ connect(m, &QAbstractItemModel::modelReset, this, &TableViewBase::restoreHeaderState);
+ connect(m, &QAbstractItemModel::columnsInserted, this, &TableViewBase::restoreHeaderState);
+ connect(m, &QAbstractItemModel::columnsRemoved, this, &TableViewBase::restoreHeaderState);
+
+ // Grab default header state
+ QTimer::singleShot(0, this, [this]{
+ captureDefaultHeaderState();
+ });
+
+ QMetaObject::invokeMethod(this, "restoreHeaderState", Qt::QueuedConnection);
+ }
+
+ // Save after debounce for repeated move/drag
+ void scheduleSaveHeaderState() { m_headerSaveDebounce.start(); }
+
+Q_SIGNALS:
+ // For owners/derived classes to add their own menu items
+ void populateHeaderContextMenu(QMenu*, const QPoint&);
+
+protected:
+ QString viewName() const {
+ if (!m_viewName.isEmpty()) return m_viewName;
+ if (!objectName().isEmpty()) return objectName();
+ return metaObject()->className();
+ }
+
+ QString settingsKey(const QString& suffix) const {
+ return QStringLiteral("tables/%1/%2").arg(viewName(), suffix);
+ }
+
+ void saveHeaderState() const {
+ auto* hh = horizontalHeader();
+ if (!hh) return;
+ QSettings s;
+ s.setValue(settingsKey("horizontalHeaderState"), hh->saveState());
+ }
+
+ void restoreHeaderState() const
+ {
+ auto* hh = horizontalHeader();
+ if (!hh) return;
+ QSettings s;
+ const QByteArray st = s.value(settingsKey("horizontalHeaderState")).toByteArray();
+ if (!st.isEmpty()) hh->restoreState(st);
+
+ const QByteArray def = s.value(settingsKey("horizontalHeaderDefaultState")).toByteArray();
+ if (!def.isEmpty()) hh->restoreState(def);
+ }
+
+ virtual int defaultSectionWidth(const int logicalIndex, const int charWidth) const
+ {
+ QString headerText;
+ if (model()) {
+ headerText = model()->headerData(logicalIndex, Qt::Horizontal, Qt::DisplayRole).toString();
+ }
+ constexpr int minChars = 8;
+ const int headerChars = qMax(minChars, headerText.size() + 2);
+ return headerChars * charWidth;
+ }
+
+ /// Grabs default header states on startup to save
+ /// Kind of a hacky fix, but many of our tables have manually set widths,
+ /// so compensating for them is a hassle.
+ void captureDefaultHeaderState() const
+ {
+ auto* hh = horizontalHeader();
+ if (!hh) return;
+
+ QSettings s;
+ const auto key = settingsKey("horizontalHeaderDefaultState");
+ if (!s.contains(key)) {
+ s.setValue(key, hh->saveState());
+ s.sync();
+ }
+ }
+
+
+ void resetColumnLayout() const
+ {
+ auto* hh = horizontalHeader();
+ if (!hh || !model()) return;
+
+ {
+ QSettings s;
+ s.remove(settingsKey("horizontalHeaderState"));
+ }
+
+ QSettings s;
+ const QByteArray def = s.value(settingsKey("horizontalHeaderDefaultState")).
+ toByteArray();
+ if (!def.isEmpty() && hh->restoreState(def))
+ {
+ return;
+ }
+
+ // If no default or set user layout, use size hints
+ for (int c = 0; c < model()->columnCount(); ++c)
+ {
+ constexpr int extra = 12;
+ int w = sizeHintForColumn(c);
+ QVariant head = model()->headerData(c, Qt::Horizontal, Qt::SizeHintRole);
+ int headerW = hh->sectionSizeHint(c);
+ if (head.canConvert<QSize>())
+ headerW = std::max(headerW, head.toSize().width());
+ hh->resizeSection(c, std::max(w, headerW) + extra);
+ }
+ }
+
+
+private:
+ QString m_viewName;
+ QTimer m_headerSaveDebounce;
+
+};
diff --git a/ui/tagtypelist.h b/ui/tagtypelist.h
index 62950405..ee6793c3 100644
--- a/ui/tagtypelist.h
+++ b/ui/tagtypelist.h
@@ -8,6 +8,7 @@
#include <QtWidgets/QComboBox>
#include "binaryninjaapi.h"
#include "viewframe.h"
+#include "tableviewbase.h"
#define TAGS_UPDATE_CHECK_INTERVAL 200
@@ -88,7 +89,7 @@ class BINARYNINJAUIAPI TagTypeItemDelegate : public QItemDelegate
\ingroup tagtypelist
*/
-class BINARYNINJAUIAPI TagTypeList : public QTableView, public BinaryNinja::BinaryDataNotification
+class BINARYNINJAUIAPI TagTypeList : public TableViewBase, public BinaryNinja::BinaryDataNotification
{
Q_OBJECT