diff options
| author | Alexander Khosrowshahi <alexk@vector35.com> | 2025-08-28 16:03:23 -0400 |
|---|---|---|
| committer | Jordan <github@psifertex.com> | 2025-10-24 11:48:53 -0400 |
| commit | 4e3ef2ce073efe0cf1c7b744e720202afe7e294e (patch) | |
| tree | 9ef816922564b6a5d917ca8f8b8cb9038526d57e /ui | |
| parent | 8ffd2aa208552bcfef8d5436b62826d17f95ae86 (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.h | 6 | ||||
| -rw-r--r-- | ui/searchresult.h | 3 | ||||
| -rw-r--r-- | ui/stringsview.h | 7 | ||||
| -rw-r--r-- | ui/tableviewbase.h | 169 | ||||
| -rw-r--r-- | ui/tagtypelist.h | 3 |
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 |
