diff options
| -rw-r--r-- | view/kernelcache/ui/kctriage.cpp | 179 | ||||
| -rw-r--r-- | view/kernelcache/ui/kctriage.h | 238 | ||||
| -rw-r--r-- | view/sharedcache/ui/dsctriage.cpp | 149 | ||||
| -rw-r--r-- | view/sharedcache/ui/dsctriage.h | 73 | ||||
| -rw-r--r-- | view/sharedcache/ui/symboltable.cpp | 51 | ||||
| -rw-r--r-- | view/sharedcache/ui/symboltable.h | 47 |
6 files changed, 371 insertions, 366 deletions
diff --git a/view/kernelcache/ui/kctriage.cpp b/view/kernelcache/ui/kctriage.cpp index 7b3cf9f0..aa2d7bb1 100644 --- a/view/kernelcache/ui/kctriage.cpp +++ b/view/kernelcache/ui/kctriage.cpp @@ -1,152 +1,14 @@ -#include "globalarea.h" -#include "kctriage.h" -#include "progresstask.h" -#include "ui/fontsettings.h" #include <QMessageBox> #include <QPainter> #include <cmath> +#include "globalarea.h" +#include "kctriage.h" +#include "ui/fontsettings.h" using namespace BinaryNinja; using namespace KernelCacheAPI; -SymbolTableModel::SymbolTableModel(SymbolTableView* parent) - : QAbstractTableModel(parent), m_parent(parent) { - // TODO: Need to implement updating this font if it is changed by the user - m_font = getMonospaceFont(parent); -} - - -int SymbolTableModel::rowCount(const QModelIndex& parent) const { - Q_UNUSED(parent); - return static_cast<int>(m_symbols.size()); -} - - -int SymbolTableModel::columnCount(const QModelIndex& parent) const { - Q_UNUSED(parent); - // We have 3 columns: Address, Name, and Image - return 3; -} - - -QVariant SymbolTableModel::data(const QModelIndex& index, int role) const { - if (!index.isValid() || (role != Qt::DisplayRole && role != Qt::FontRole)) { - return QVariant(); - } - - const KCSymbol& symbol = m_symbols.at(index.row()); - - switch (role) - { - case Qt::DisplayRole: - { - switch (index.column()) { - case 0: // Address column - return QString("0x%1").arg(symbol.address, 0, 16); // Display address as hexadecimal - case 1: // Name column - return QString::fromStdString(symbol.name); - case 2: // Image column - return QString::fromStdString(symbol.image); - default: - return QVariant(); - } - } - case Qt::FontRole: - return m_font; - default: - return QVariant(); - } -} - - -QVariant SymbolTableModel::headerData(int section, Qt::Orientation orientation, int role) const { - if (role != Qt::DisplayRole || orientation != Qt::Horizontal) { - return QVariant(); - } - - switch (section) { - case 0: - return QString("Address"); - case 1: - return QString("Name"); - case 2: - return QString("Image"); - default: - return QVariant(); - } -} - - -void SymbolTableModel::updateSymbols() { - m_symbols = m_parent->m_symbols; - setFilter(m_filter); -} - - -const KCSymbol& SymbolTableModel::symbolAt(int row) const { - return m_symbols.at(row); -} - - -void SymbolTableModel::setFilter(std::string text) -{ - beginResetModel(); - - m_filter = text; - m_symbols.clear(); - - if (!m_filter.empty()) - { - m_symbols.reserve(m_parent->m_symbols.size()); - for (const auto& symbol : m_parent->m_symbols) - if (symbol.name.find(m_filter) != std::string::npos) - m_symbols.push_back(symbol); - m_symbols.shrink_to_fit(); - } - else - { - m_symbols = m_parent->m_symbols; - } - - endResetModel(); -} - - -SymbolTableView::SymbolTableView(QWidget* parent, Ref<KernelCache> cache) - : QTableView(parent), m_model(new SymbolTableModel(this)) { - - // Set up the filter model - setModel(m_model); - - // Configure view settings - horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); - setEditTriggers(QAbstractItemView::NoEditTriggers); - setSelectionBehavior(QAbstractItemView::SelectRows); - setSelectionMode(QAbstractItemView::SingleSelection); - - setSortingEnabled(true); - - BackgroundThread::create(this)->thenBackground([this, cache](){ - // LogInfo("Symbol Search: Loading symbols..."); - m_symbols = cache->LoadAllSymbolsAndWait(); - // LogInfo("Symbol Search: Loaded 0x%zx symbols", m_symbols.size()); - })->thenMainThread([this](){ - m_model->updateSymbols(); - })->start(); -} - - -SymbolTableView::~SymbolTableView() { - delete m_model; -} - - -void SymbolTableView::setFilter(const std::string& filter) { - m_model->setFilter(filter); -} - - KCTriageViewType::KCTriageViewType() : ViewType("KCTriage", "Kernel Cache Triage") {} @@ -194,8 +56,6 @@ KCTriageView::KCTriageView(QWidget* parent, BinaryViewRef data) : QWidget(parent m_layout->addWidget(m_triageTabs); setLayout(m_layout); - // XXX: RefreshData - m_triageTabs->selectWidget(defaultWidget); } @@ -261,18 +121,18 @@ QWidget* KCTriageView::initImageTable() auto loadImageButton = new QPushButton(); connect(loadImageButton, &QPushButton::clicked, [this](bool) { // Collect only visible selected rows - QModelIndexList visibleSelectedRows; + QModelIndexList selected; for (const auto& index : m_imageTable->selectionModel()->selectedRows()) { if (!m_imageTable->isRowHidden(index.row())) { - visibleSelectedRows.append(index); + selected.append(index); } } - if (visibleSelectedRows.empty()) + if (selected.empty()) return; - for (const auto& selection : visibleSelectedRows) { - auto name = selection.data().toString().toStdString(); + for (const auto& selection : selected) { + auto name = m_imageModel->item(selection.row(), 1)->text().toStdString(); WorkerPriorityEnqueue([this, name]() { m_cache->LoadImageWithInstallName(name); }); } }); @@ -284,7 +144,7 @@ QWidget* KCTriageView::initImageTable() }); connect(m_imageTable, &FilterableTableView::activated, this, [=](const QModelIndex& index) { - auto selected = m_imageModel->item(index.row(), 0); + auto selected = m_imageModel->item(index.row(), 1); auto name = selected->text().toStdString(); WorkerPriorityEnqueue([this, name]() { m_cache->LoadImageWithInstallName(name); }); }); @@ -292,12 +152,11 @@ QWidget* KCTriageView::initImageTable() auto loadImageLayout = new QVBoxLayout; loadImageLayout->addWidget(loadImageFilterEdit); loadImageLayout->addWidget(m_imageTable); - loadImageLayout->addWidget(loadImageButton); - auto buttonLayout = new QHBoxLayout; - buttonLayout->addWidget(loadImageButton); - buttonLayout->setAlignment(Qt::AlignLeft); - loadImageLayout->addLayout(buttonLayout); + auto loadImageFooterLayout = new QHBoxLayout; + loadImageFooterLayout->addWidget(loadImageButton); + loadImageFooterLayout->setAlignment(Qt::AlignLeft); + loadImageLayout->addLayout(loadImageFooterLayout); auto loadImageWidget = new QWidget; loadImageWidget->setLayout(loadImageLayout); @@ -306,7 +165,7 @@ QWidget* KCTriageView::initImageTable() m_imageTable->setEditTriggers(QAbstractItemView::NoEditTriggers); - m_imageTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed); + m_imageTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); m_imageTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); m_imageTable->setSelectionBehavior(QAbstractItemView::SelectRows); @@ -332,9 +191,6 @@ void KCTriageView::initSymbolTable() m_symbolTable->setFilter(filter.toStdString()); }); - // Apply custom column styling - m_symbolTable->setItemDelegateForColumn(0, new AddressColorDelegate(m_symbolTable)); - auto symbolLayout = new QVBoxLayout; symbolLayout->addWidget(symbolFilterEdit); symbolLayout->addWidget(m_symbolTable); @@ -342,13 +198,6 @@ void KCTriageView::initSymbolTable() auto symbolWidget = new QWidget; symbolWidget->setLayout(symbolLayout); - m_symbolTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); // Address - m_symbolTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); // Name - m_symbolTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch); // Image - - m_symbolTable->setSelectionBehavior(QAbstractItemView::SelectRows); - m_symbolTable->setSelectionMode(QAbstractItemView::SingleSelection); - std::function<void(uint64_t)> navigateToAddress = [=](uint64_t addr) { ExecuteOnMainThread([addr, this](){ if (Settings::Instance()->Get<bool>("ui.view.graph.preferred")) 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)); + } }; diff --git a/view/sharedcache/ui/dsctriage.cpp b/view/sharedcache/ui/dsctriage.cpp index e2cf8147..54fe4ed0 100644 --- a/view/sharedcache/ui/dsctriage.cpp +++ b/view/sharedcache/ui/dsctriage.cpp @@ -1,11 +1,10 @@ +#include <QHeaderView> +#include <QMessageBox> +#include <utility> #include "dsctriage.h" #include "globalarea.h" -#include "progresstask.h" #include "symboltable.h" #include "ui/fontsettings.h" -#include <QHeaderView> -#include <QMessageBox> -#include <utility> using namespace BinaryNinja; using namespace SharedCacheAPI; @@ -28,7 +27,6 @@ QWidget* DSCTriageViewType::create(BinaryViewRef data, ViewFrame* viewFrame) { if (data->GetTypeName() != VIEW_NAME) return nullptr; - // TODO: Check for dyld start. Then continue. return new DSCTriageView(viewFrame, data); } @@ -134,36 +132,36 @@ void DSCTriageView::loadImagesWithAddr(const std::vector<uint64_t>& addresses, b QWidget* DSCTriageView::initImageTable() { - auto loadImageTable = new FilterableTableView(this); + m_imageTable = new FilterableTableView(this); - m_imageModel = new QStandardItemModel(0, 3, loadImageTable); + m_imageModel = new QStandardItemModel(0, 3, m_imageTable); m_imageModel->setHorizontalHeaderLabels({"Address", "Loaded", "Name"}); // Apply custom column styling - loadImageTable->setItemDelegateForColumn(0, new AddressColorDelegate(loadImageTable)); - loadImageTable->setItemDelegateForColumn(1, new LoadedDelegate(loadImageTable)); + m_imageTable->setItemDelegateForColumn(0, new AddressColorDelegate(m_imageTable)); + m_imageTable->setItemDelegateForColumn(1, new LoadedDelegate(m_imageTable)); // Context menu - loadImageTable->setContextMenuPolicy(Qt::CustomContextMenu); - connect(loadImageTable, &QWidget::customContextMenuRequested, [this, loadImageTable](const QPoint &pos) { - QMenu contextMenu(tr("Load Image Actions"), loadImageTable); + m_imageTable->setContextMenuPolicy(Qt::CustomContextMenu); + connect(m_imageTable, &QWidget::customContextMenuRequested, [this](const QPoint &pos) { + QMenu contextMenu(tr("Load Image Actions"), m_imageTable); // Get number of selected images - auto selected = loadImageTable->selectionModel()->selectedRows(); + auto selected = m_imageTable->selectionModel()->selectedRows(); int selectedCount = 0; std::vector<uint64_t> addresses; for (const auto& idx : selected) { // Skip rows hidden by the filter - if (loadImageTable->isRowHidden(idx.row())) + if (m_imageTable->isRowHidden(idx.row())) continue; addresses.push_back(idx.data().toString().toULongLong(nullptr, 16)); selectedCount++; } - QAction noSelectionAction("No Images Selected", loadImageTable); - QAction loadImagesAction("", loadImageTable); - QAction loadImagesWithDepsAction("", loadImageTable); + QAction noSelectionAction("No Images Selected", m_imageTable); + QAction loadImagesAction("", m_imageTable); + QAction loadImagesWithDepsAction("", m_imageTable); if (selectedCount == 0) { noSelectionAction.setEnabled(false); @@ -188,20 +186,25 @@ QWidget* DSCTriageView::initImageTable() contextMenu.addAction(&loadImagesWithDepsAction); } - contextMenu.exec(loadImageTable->viewport()->mapToGlobal(pos)); + contextMenu.exec(m_imageTable->viewport()->mapToGlobal(pos)); }); auto loadImageButton = new QPushButton(); - connect(loadImageButton, &QPushButton::clicked, [this, loadImageTable](bool) { - auto selected = loadImageTable->selectionModel()->selectedRows(); + connect(loadImageButton, &QPushButton::clicked, [this](bool) { + // Collect only visible selected rows + QModelIndexList selected; + for (const auto& index : m_imageTable->selectionModel()->selectedRows()) { + if (!m_imageTable->isRowHidden(index.row())) { + selected.append(index); + } + } + + if (selected.empty()) + return; + std::vector<uint64_t> addresses; for (const auto& idx : selected) - { - // Skip rows hidden by the filter - if (loadImageTable->isRowHidden(idx.row())) - continue; addresses.push_back(idx.data().toString().toULongLong(nullptr, 16)); - } loadImagesWithAddr(addresses); }); loadImageButton->setText("Load Selected"); @@ -213,43 +216,43 @@ QWidget* DSCTriageView::initImageTable() refreshDataButton->setText("Refresh"); } // refreshDataButton - auto loadImageFilterEdit = new FilterEdit(loadImageTable); - connect(loadImageFilterEdit, &FilterEdit::textChanged, [loadImageTable](const QString& filter) { - loadImageTable->setFilter(filter.toStdString()); + auto loadImageFilterEdit = new FilterEdit(m_imageTable); + connect(loadImageFilterEdit, &FilterEdit::textChanged, [this](const QString& filter) { + m_imageTable->setFilter(filter.toStdString()); }); - connect(loadImageTable, &FilterableTableView::activated, this, [=](const QModelIndex& index) { + connect(m_imageTable, &FilterableTableView::activated, this, [=](const QModelIndex& index) { auto addr = m_imageModel->item(index.row(), 0)->text().toULongLong(nullptr, 16); loadImagesWithAddr({addr}); }); auto loadImageLayout = new QVBoxLayout; loadImageLayout->addWidget(loadImageFilterEdit); - loadImageLayout->addWidget(loadImageTable); + loadImageLayout->addWidget(m_imageTable); - auto buttonLayout = new QHBoxLayout; - buttonLayout->addWidget(loadImageButton); - buttonLayout->addWidget(refreshDataButton); - buttonLayout->setAlignment(Qt::AlignLeft); - loadImageLayout->addLayout(buttonLayout); + auto loadImageFooterLayout = new QHBoxLayout; + loadImageFooterLayout->addWidget(loadImageButton); + loadImageFooterLayout->addWidget(refreshDataButton); + loadImageFooterLayout->setAlignment(Qt::AlignLeft); + loadImageLayout->addLayout(loadImageFooterLayout); auto loadImageWidget = new QWidget; loadImageWidget->setLayout(loadImageLayout); - loadImageTable->setModel(m_imageModel); + m_imageTable->setModel(m_imageModel); - loadImageTable->setEditTriggers(QAbstractItemView::NoEditTriggers); + m_imageTable->setEditTriggers(QAbstractItemView::NoEditTriggers); - loadImageTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed); - loadImageTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); - loadImageTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch); + m_imageTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed); + m_imageTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + m_imageTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch); - loadImageTable->setSelectionBehavior(QAbstractItemView::SelectRows); - loadImageTable->setSelectionMode(QAbstractItemView::ExtendedSelection); + m_imageTable->setSelectionBehavior(QAbstractItemView::SelectRows); + m_imageTable->setSelectionMode(QAbstractItemView::ExtendedSelection); - loadImageTable->setSortingEnabled(true); + m_imageTable->setSortingEnabled(true); - loadImageTable->verticalHeader()->setVisible(false); + m_imageTable->verticalHeader()->setVisible(false); m_triageTabs->addTab(loadImageWidget, "Images"); m_triageTabs->setCanCloseTab(loadImageWidget, false); @@ -262,51 +265,43 @@ void DSCTriageView::initSymbolTable() { m_symbolTable = new SymbolTableView(this); + // Apply custom column styling + m_symbolTable->setItemDelegateForColumn(0, new AddressColorDelegate(m_symbolTable)); + auto symbolFilterEdit = new FilterEdit(m_symbolTable); connect(symbolFilterEdit, &FilterEdit::textChanged, [this](const QString& filter) { m_symbolTable->setFilter(filter.toStdString()); }); - // Apply custom column styling - m_symbolTable->setItemDelegateForColumn(0, new AddressColorDelegate(m_symbolTable)); - auto loadSymbolImageButton = new QPushButton(); - { - connect(loadSymbolImageButton, &QPushButton::clicked, - [this](bool) { - auto selected = m_symbolTable->selectionModel()->selectedRows(); - std::vector<uint64_t> addresses; - for (const auto& row : selected) - addresses.push_back(row.data().toString().toULongLong(nullptr, 16)); - loadImagesWithAddr(addresses); - }); - loadSymbolImageButton->setText("Load Image"); - } // loadImageButton + connect(loadSymbolImageButton, &QPushButton::clicked, [this](bool) { + auto selected = m_symbolTable->selectionModel()->selectedRows(); + std::vector<uint64_t> addresses; + for (const auto& row : selected) + addresses.push_back(row.data().toString().toULongLong(nullptr, 16)); + loadImagesWithAddr(addresses); + }); + loadSymbolImageButton->setText("Load Image"); // Shows the current selected rows image name. - auto currentImageLabel = new QLabel(this); { - currentImageLabel->setText(""); - currentImageLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - } - - // Update the label whenever the selection changes. - connect(m_symbolTable->selectionModel(), &QItemSelectionModel::currentRowChanged, this, - [this, currentImageLabel](const QModelIndex ¤t, const QModelIndex &) { - auto symbol = m_symbolTable->getSymbolAtRow(current.row()); - auto controller = SharedCacheController::GetController(*this->m_data); - if (!controller) - return; - auto image = controller->GetImageContaining(symbol.address); - if (image) - currentImageLabel->setText("Image: " + QString::fromStdString(image->name)); - else - currentImageLabel->setText(""); - }); + auto currentImageLabel = new QLabel(this); + currentImageLabel->setText(""); + currentImageLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + connect(m_symbolTable->selectionModel(), &QItemSelectionModel::currentRowChanged, this, [this, currentImageLabel](const QModelIndex ¤t, const QModelIndex &) { + auto symbol = m_symbolTable->getSymbolAtRow(current.row()); + auto controller = SharedCacheController::GetController(*this->m_data); + if (!controller) + return; + auto image = controller->GetImageContaining(symbol.address); + if (image) + currentImageLabel->setText("Image: " + QString::fromStdString(image->name)); + else + currentImageLabel->setText(""); + }); auto symbolFooterLayout = new QHBoxLayout; symbolFooterLayout->addWidget(loadSymbolImageButton); symbolFooterLayout->addWidget(currentImageLabel); - symbolFooterLayout->setAlignment(Qt::AlignLeft); auto symbolLayout = new QVBoxLayout; diff --git a/view/sharedcache/ui/dsctriage.h b/view/sharedcache/ui/dsctriage.h index a16457e3..e847625e 100644 --- a/view/sharedcache/ui/dsctriage.h +++ b/view/sharedcache/ui/dsctriage.h @@ -1,24 +1,29 @@ -#include <binaryninjaapi.h> +#include <QHeaderView> #include <QItemDelegate> +#include <QPainter> +#include <QSortFilterProxyModel> +#include <QStandardItemModel> #include <QStyledItemDelegate> - -#include "uitypes.h" -#include "viewframe.h" -#include "animation.h" -#include "uicontext.h" - +#include <QTableView> +#include <binaryninjaapi.h> +#include <progresstask.h> +#include <sharedcacheapi.h> #include "filter.h" #include "symboltable.h" - #include "ui/fontsettings.h" +#include "uicontext.h" +#include "uitypes.h" +#include "viewframe.h" #ifndef BINARYNINJA_DSCTRIAGE_H #define BINARYNINJA_DSCTRIAGE_H +using namespace BinaryNinja; +using namespace SharedCacheAPI; + class AddressColorDelegate : public QStyledItemDelegate { - public: explicit AddressColorDelegate(QObject* parent = nullptr) : QStyledItemDelegate(parent) {} @@ -27,6 +32,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; @@ -130,7 +136,10 @@ public: void scrollToFirstItem() override { if (model()->rowCount() > 0) { - scrollTo(model()->index(0, 0)); + QModelIndex top = indexAt(rect().topLeft()); + if (top.isValid()) { + scrollTo(top); + } } } @@ -143,34 +152,39 @@ 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); } +// 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; +// } +// } +// return QTableView::eventFilter(obj, event); +// } + signals: void filterTextChanged(const QString& text); }; @@ -184,6 +198,7 @@ class DSCTriageView : public QWidget, public View, public UIContextNotification SplitTabWidget* m_triageTabs; DockableTabCollection* m_triageCollection; + FilterableTableView* m_imageTable; QStandardItemModel* m_imageModel; SymbolTableView* m_symbolTable; diff --git a/view/sharedcache/ui/symboltable.cpp b/view/sharedcache/ui/symboltable.cpp index 1df7d529..b0247f2b 100644 --- a/view/sharedcache/ui/symboltable.cpp +++ b/view/sharedcache/ui/symboltable.cpp @@ -10,23 +10,27 @@ using namespace BinaryNinja; using namespace SharedCacheAPI; + SymbolTableModel::SymbolTableModel(SymbolTableView* parent) : QAbstractTableModel(parent), m_parent(parent) { // TODO: Need to implement updating this font if it is changed by the user m_font = getMonospaceFont(parent); } + int SymbolTableModel::rowCount(const QModelIndex& parent) const { Q_UNUSED(parent); return static_cast<int>(m_modelSymbols.size()); } + int SymbolTableModel::columnCount(const QModelIndex& parent) const { Q_UNUSED(parent); // We have 3 columns: Address, Type, Name return 3; } + QVariant SymbolTableModel::data(const QModelIndex& index, int role) const { if (!index.isValid() || (role != Qt::DisplayRole && role != Qt::FontRole)) { return QVariant(); @@ -58,6 +62,7 @@ QVariant SymbolTableModel::data(const QModelIndex& index, int role) const { } } + QVariant SymbolTableModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role != Qt::DisplayRole || orientation != Qt::Horizontal) { return QVariant(); @@ -75,12 +80,58 @@ QVariant SymbolTableModel::headerData(int section, Qt::Orientation orientation, } } + +void SymbolTableModel::sort(int column, Qt::SortOrder order) +{ + beginResetModel(); + + std::function<bool(const CacheSymbol&, const CacheSymbol&)> comparator; + + switch (column) + { + case 0: // Address column + comparator = [](const CacheSymbol& a, const CacheSymbol& b) { + return a.address < b.address; + }; + break; + case 1: // Type column + comparator = [](const CacheSymbol& a, const CacheSymbol& b) { + return GetSymbolTypeAsString(a.type) < GetSymbolTypeAsString(b.type); + }; + break; + case 2: // Name column + comparator = [](const CacheSymbol& a, const CacheSymbol& b) { + return a.name < b.name; + }; + break; + default: + endResetModel(); + return; + } + + if (order == Qt::DescendingOrder) + { + std::sort(m_modelSymbols.begin(), m_modelSymbols.end(), + [&comparator](const CacheSymbol& a, const CacheSymbol& b) { + return comparator(b, a); + }); + } + else + { + std::sort(m_modelSymbols.begin(), m_modelSymbols.end(), comparator); + } + + endResetModel(); +} + + void SymbolTableModel::updateSymbols(std::vector<CacheSymbol>&& symbols) { m_preparedSymbols = symbols; setFilter(m_filter); } + const CacheSymbol& SymbolTableModel::symbolAt(int row) const { return m_modelSymbols.at(row); diff --git a/view/sharedcache/ui/symboltable.h b/view/sharedcache/ui/symboltable.h index 2fc5c892..51ba88f0 100644 --- a/view/sharedcache/ui/symboltable.h +++ b/view/sharedcache/ui/symboltable.h @@ -2,12 +2,13 @@ #include <sharedcacheapi.h> #include "viewframe.h" -#include "animation.h" #include <QTableView> #include <QStandardItemModel> #include "filter.h" +#ifndef BINARYNINJA_DSCSYMBOLTABLE_H +#define BINARYNINJA_DSCSYMBOLTABLE_H class SymbolTableView; @@ -29,9 +30,11 @@ public: 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 sort(int column, Qt::SortOrder order) override; void updateSymbols(std::vector<SharedCacheAPI::CacheSymbol>&& symbols); void setFilter(std::string text); const SharedCacheAPI::CacheSymbol& symbolAt(int row) const; + }; @@ -44,45 +47,46 @@ Q_OBJECT public: explicit SymbolTableView(QWidget* parent); - ~SymbolTableView() override; + // Call this to populate the symbols from the given view. + void populateSymbols(BinaryNinja::BinaryView& view); + void scrollToFirstItem() override { - if (model()->rowCount() > 0) - { - scrollTo(model()->index(0, 0)); + if (model()->rowCount() > 0) { + QModelIndex top = indexAt(rect().topLeft()); + if (top.isValid()) + scrollTo(top); } } - // Call this to populate the symbols from the given view. - void populateSymbols(BinaryNinja::BinaryView& view); - void scrollToCurrentItem() override { QModelIndex currentIndex = selectionModel()->currentIndex(); if (currentIndex.isValid()) - { scrollTo(currentIndex); - } } void selectFirstItem() override { - if (model()->rowCount() > 0) - { - QModelIndex firstIndex = model()->index(0, 0); - selectionModel()->select(firstIndex, QItemSelectionModel::ClearAndSelect); + if (model()->rowCount() > 0) { + 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); + if (model()->rowCount() > 0) { + QModelIndex topLeft = indexAt(rect().topLeft()); + if (topLeft.isValid()) { + setCurrentIndex(topLeft); + emit activated(topLeft); + } } } @@ -92,4 +96,7 @@ public: } void setFilter(const std::string& filter) override; -};
\ No newline at end of file +}; + + +#endif // BINARYNINJA_DSCSYMBOLTABLE_H |
