diff options
| author | Alexander Taylor <alex@vector35.com> | 2025-04-14 11:15:01 -0400 |
|---|---|---|
| committer | Alexander Taylor <alex@vector35.com> | 2025-04-14 14:53:51 -0400 |
| commit | 507935f828188047ee009d2216a2c2d92c8b83fd (patch) | |
| tree | 4247355ef5788809c8e47fc11e345a623f053196 /view/kernelcache/ui/kctriage.h | |
| parent | 37e20cc30d57fff6b78bf6be9be2230a8ff973cc (diff) | |
Fixes for multiple issues in DSC/KC Triage views.
1. Crash on kernel cache image load
2. Duplicate Load Image buttons in kernel cache view
3. Improper selection behavior in both views
4. Address column should no longer resize to be smaller than contents
5. Symbol tables should now be properly sortable
Diffstat (limited to 'view/kernelcache/ui/kctriage.h')
| -rw-r--r-- | view/kernelcache/ui/kctriage.h | 238 |
1 files changed, 163 insertions, 75 deletions
diff --git a/view/kernelcache/ui/kctriage.h b/view/kernelcache/ui/kctriage.h index 07c24fad..7d8bdfc1 100644 --- a/view/kernelcache/ui/kctriage.h +++ b/view/kernelcache/ui/kctriage.h @@ -1,25 +1,26 @@ -#include <kernelcacheapi.h> -#include <binaryninjaapi.h> -#include "uitypes.h" -#include "viewframe.h" -#include "animation.h" -#include "uicontext.h" - -#include <QTableView> -#include <QStandardItemModel> -#include <QSortFilterProxyModel> #include <QHeaderView> -#include "filter.h" +#include <QSortFilterProxyModel> +#include <QStandardItemModel> #include <QStyledItemDelegate> - +#include <QTableView> +#include <binaryninjaapi.h> +#include <kernelcacheapi.h> +#include <progresstask.h> +#include "filter.h" #include "ui/fontsettings.h" +#include "uicontext.h" +#include "uitypes.h" +#include "viewframe.h" #ifndef BINARYNINJA_KCTRIAGE_H #define BINARYNINJA_KCTRIAGE_H + +using namespace KernelCacheAPI; + + class AddressColorDelegate : public QStyledItemDelegate { - public: explicit AddressColorDelegate(QObject* parent = nullptr) : QStyledItemDelegate(parent) {} @@ -28,6 +29,7 @@ public: QStyleOptionViewItem opt = option; initStyleOption(&opt, index); + opt.font = getMonospaceFont(qobject_cast<QWidget*>(parent())); opt.palette.setColor(QPalette::Text, getThemeColor(BNThemeColor::AddressColor)); opt.displayAlignment = Qt::AlignCenter | Qt::AlignVCenter; @@ -36,6 +38,21 @@ public: }; +class MonospaceFontDelegate : public QStyledItemDelegate { +public: + explicit MonospaceFontDelegate(QObject* parent = nullptr) : QStyledItemDelegate(parent) {} + + void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override { + QStyleOptionViewItem opt = option; + initStyleOption(&opt, index); + + opt.font = getMonospaceFont(qobject_cast<QWidget*>(parent())); + + QStyledItemDelegate::paint(painter, opt, index); + } +}; + + class FilterableTableView : public QTableView, public FilterTarget { Q_OBJECT @@ -73,7 +90,10 @@ public: void scrollToFirstItem() override { if (model()->rowCount() > 0) { - scrollTo(model()->index(0, 0)); + QModelIndex top = indexAt(rect().topLeft()); + if (top.isValid()) { + scrollTo(top); + } } } @@ -86,109 +106,177 @@ public: void selectFirstItem() override { if (model()->rowCount() > 0) { - QModelIndex firstIndex = model()->index(0, 0); - selectionModel()->select(firstIndex, QItemSelectionModel::ClearAndSelect); + QModelIndex top = indexAt(rect().topLeft()); + if (top.isValid()) { + selectionModel()->select(top, QItemSelectionModel::ClearAndSelect); + setCurrentIndex(top); + } } } void activateFirstItem() override { if (model()->rowCount() > 0) { - QModelIndex firstIndex = model()->index(0, 0); - setCurrentIndex(firstIndex); - emit activated(firstIndex); - } - } - - bool eventFilter(QObject* obj, QEvent* event) override { - if (event->type() == QEvent::KeyPress) { - auto keyEvent = dynamic_cast<QKeyEvent*>(event); - if (keyEvent->key() == Qt::Key_Escape) { - clearSelection(); - return true; - } - if (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) { - emit activated(currentIndex()); - return true; + QModelIndex topLeft = indexAt(rect().topLeft()); + if (topLeft.isValid()) { + setCurrentIndex(topLeft); + emit activated(topLeft); } } - return QTableView::eventFilter(obj, event); } signals: void filterTextChanged(const QString& text); }; -class SymbolTableView; - -class SymbolTableModel : public QAbstractTableModel { - Q_OBJECT - SymbolTableView* m_parent; - QFont m_font; - std::string m_filter; - std::vector<KernelCacheAPI::KCSymbol> m_symbols; +class SymbolTableProxyModel : public QSortFilterProxyModel +{ +Q_OBJECT public: - explicit SymbolTableModel(SymbolTableView* parent); + SymbolTableProxyModel(QObject* parent = nullptr) : QSortFilterProxyModel(parent), m_timer(new QTimer(this)) + { + m_timer->setSingleShot(true); + connect(m_timer, &QTimer::timeout, this, &SymbolTableProxyModel::delayedFilterChanged); + } + + void setFilterString(const QString& filter) + { + QRegularExpression newRegEx(QRegularExpression::escape(filter), QRegularExpression::CaseInsensitiveOption); + if (m_filter != newRegEx) { + m_filter = std::move(newRegEx); + m_timer->start(200); + } + } - int rowCount(const QModelIndex& parent) const override; - int columnCount(const QModelIndex& parent) const override; - QVariant data(const QModelIndex& index, int role) const override; - QVariant headerData(int section, Qt::Orientation orientation, int role) const override; - void updateSymbols(); - void setFilter(std::string text); +protected: + bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override + { + if (m_filter.pattern().isEmpty()) + return true; - const KernelCacheAPI::KCSymbol& symbolAt(int row) const; + for (int column = 0; column < sourceModel()->columnCount(source_parent); ++column) + { + QModelIndex index = sourceModel()->index(source_row, column, source_parent); + QString data = sourceModel()->data(index).toString(); + if (m_filter.match(data).hasMatch()) + return true; + } + return false; + } + +private slots: + void delayedFilterChanged() + { + invalidateFilter(); + } + +private: + QRegularExpression m_filter; + QTimer* m_timer; }; class SymbolTableView : public QTableView, public FilterTarget { - Q_OBJECT +Q_OBJECT friend class SymbolTableModel; std::vector<KernelCacheAPI::KCSymbol> m_symbols; - - SymbolTableModel* m_model; + QStandardItemModel* m_model; + SymbolTableProxyModel* m_proxyModel; public: - SymbolTableView(QWidget* parent, Ref<KernelCacheAPI::KernelCache> cache); - ~SymbolTableView() override; + SymbolTableView(QWidget* parent, Ref<KernelCache>& cache) + : QTableView(parent), m_model(new QStandardItemModel(this)), m_proxyModel(new SymbolTableProxyModel(this)) + { + m_proxyModel->setSourceModel(m_model); + setModel(m_proxyModel); - void scrollToFirstItem() override { - if (model()->rowCount() > 0) { - scrollTo(model()->index(0, 0)); - } + // Set up the headers + m_model->setColumnCount(3); + m_model->setHorizontalHeaderLabels({"Address", "Name", "Image"}); + setFont(getMonospaceFont(parent)); + setItemDelegateForColumn(0, new AddressColorDelegate(this)); + setItemDelegateForColumn(1, new MonospaceFontDelegate(this)); + setItemDelegateForColumn(2, new MonospaceFontDelegate(this)); + + // Configure view settings + horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); + horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); + horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch); + setEditTriggers(QAbstractItemView::NoEditTriggers); + setSelectionBehavior(QAbstractItemView::SelectRows); + setSelectionMode(QAbstractItemView::SingleSelection); + verticalHeader()->setVisible(false); + + setSortingEnabled(true); + + BackgroundThread::create(this)->thenBackground([this, cache](){ + m_symbols = cache->LoadAllSymbolsAndWait(); + })->thenMainThread([this](){ + updateSymbols(); + })->start(); } - void scrollToCurrentItem() override { - QModelIndex currentIndex = selectionModel()->currentIndex(); - if (currentIndex.isValid()) { - scrollTo(currentIndex); + ~SymbolTableView() override = default; + + void updateSymbols() + { + m_model->removeRows(0, m_model->rowCount()); + for (const auto& symbol : m_symbols) + { + QList<QStandardItem*> row; + row << new QStandardItem(QString("0x%1").arg(symbol.address, 0, 16)) + << new QStandardItem(QString::fromStdString(symbol.name)) + << new QStandardItem(QString::fromStdString(symbol.image)); + m_model->appendRow(row); } } - void selectFirstItem() override { - if (model()->rowCount() > 0) { - QModelIndex firstIndex = model()->index(0, 0); - selectionModel()->select(firstIndex, QItemSelectionModel::ClearAndSelect); - } + KernelCacheAPI::KCSymbol getSymbolAtRow(int row) const + { + QModelIndex proxyIndex = m_proxyModel->index(row, 0); + QModelIndex sourceIndex = m_proxyModel->mapToSource(proxyIndex); + return m_symbols[sourceIndex.row()]; } - void activateFirstItem() override { - if (model()->rowCount() > 0) { - QModelIndex firstIndex = model()->index(0, 0); - setCurrentIndex(firstIndex); - emit activated(firstIndex); + void scrollToFirstItem() override + { + scrollToTop(); + } + + void scrollToCurrentItem() override + { + scrollTo(selectionModel()->currentIndex()); + } + + void selectFirstItem() override + { + if (m_proxyModel->rowCount() > 0) { + QModelIndex idx = m_proxyModel->index(0, 0); + if (idx.isValid()) { + selectionModel()->select(idx, QItemSelectionModel::ClearAndSelect); + setCurrentIndex(idx); + } } } - KernelCacheAPI::KCSymbol getSymbolAtRow(int row) const + void activateFirstItem() override { - return m_model->symbolAt(row); + if (m_proxyModel->rowCount() > 0) { + QModelIndex idx = m_proxyModel->index(0, 0); + if (idx.isValid()) { + setCurrentIndex(idx); + emit activated(idx); + } + } } - void setFilter(const std::string& filter) override; + void setFilter(const std::string& text) override + { + m_proxyModel->setFilterString(QString::fromStdString(text)); + } }; |
