From a30ac22056d89627da0010a8435bdab71c461361 Mon Sep 17 00:00:00 2001 From: Paul I Date: Thu, 3 May 2018 10:52:30 +0300 Subject: [PATCH] Refactor SectionsWidget to use Model/View archictecture (#475) --- src/Cutter.h | 1 + src/Cutter.pro | 4 +- src/MainWindow.cpp | 3 +- src/MainWindow.h | 4 +- src/widgets/ExportsWidget.cpp | 2 - src/widgets/ExportsWidget.h | 12 +- src/widgets/SectionsDock.cpp | 71 ----- src/widgets/SectionsDock.h | 40 --- src/widgets/SectionsWidget.cpp | 290 ++++++++++++++---- src/widgets/SectionsWidget.h | 83 ++++- .../{SectionsDock.ui => SectionsWidget.ui} | 5 +- 11 files changed, 297 insertions(+), 218 deletions(-) delete mode 100644 src/widgets/SectionsDock.cpp delete mode 100644 src/widgets/SectionsDock.h rename src/widgets/{SectionsDock.ui => SectionsWidget.ui} (83%) diff --git a/src/Cutter.h b/src/Cutter.h index 55697517..37f3a432 100644 --- a/src/Cutter.h +++ b/src/Cutter.h @@ -275,6 +275,7 @@ Q_DECLARE_METATYPE(ResourcesDescription) Q_DECLARE_METATYPE(VTableDescription) Q_DECLARE_METATYPE(TypeDescription) Q_DECLARE_METATYPE(SearchDescription) +Q_DECLARE_METATYPE(SectionDescription) class CutterCore: public QObject { diff --git a/src/Cutter.pro b/src/Cutter.pro index 80348d0d..6b404c3f 100644 --- a/src/Cutter.pro +++ b/src/Cutter.pro @@ -120,7 +120,6 @@ SOURCES += \ widgets/PieView.cpp \ widgets/RelocsWidget.cpp \ widgets/SdbDock.cpp \ - widgets/SectionsDock.cpp \ widgets/SectionsWidget.cpp \ widgets/Sidebar.cpp \ widgets/StringsWidget.cpp \ @@ -192,7 +191,6 @@ HEADERS += \ widgets/PieView.h \ widgets/RelocsWidget.h \ widgets/SdbDock.h \ - widgets/SectionsDock.h \ widgets/SectionsWidget.h \ widgets/Sidebar.h \ widgets/StringsWidget.h \ @@ -252,7 +250,7 @@ FORMS += \ widgets/ImportsWidget.ui \ widgets/SdbDock.ui \ widgets/RelocsWidget.ui \ - widgets/SectionsDock.ui \ + widgets/SectionsWidget.ui \ widgets/Sidebar.ui \ widgets/StringsWidget.ui \ widgets/SymbolsWidget.ui \ diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 67889a39..3e413ee1 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -53,7 +53,6 @@ #include "widgets/SearchWidget.h" #include "widgets/SymbolsWidget.h" #include "widgets/StringsWidget.h" -#include "widgets/SectionsDock.h" #include "widgets/RelocsWidget.h" #include "widgets/FlagsWidget.h" #include "widgets/VisualNavbar.h" @@ -163,7 +162,7 @@ void MainWindow::initUI() // Hide centralWidget as we do not need it ui->centralWidget->hide(); - sectionsDock = new SectionsDock(this, ui->actionSections); + sectionsDock = new SectionsWidget(this, ui->actionSections); entrypointDock = new EntrypointWidget(this, ui->actionEntrypoints); functionsDock = new FunctionsWidget(this, ui->actionFunctions); importsDock = new ImportsWidget(this, ui->actionImports); diff --git a/src/MainWindow.h b/src/MainWindow.h index bd81a36f..9ea9930e 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -32,7 +32,7 @@ class Dashboard; class QLineEdit; class SdbDock; class QAction; -class SectionsDock; +class SectionsWidget; class ConsoleWidget; class EntrypointWidget; class DisassemblerGraphView; @@ -188,7 +188,7 @@ private: Dashboard *dashboardDock = nullptr; QLineEdit *gotoEntry = nullptr; SdbDock *sdbDock = nullptr; - SectionsDock *sectionsDock = nullptr; + SectionsWidget *sectionsDock = nullptr; ConsoleWidget *consoleDock = nullptr; ClassesWidget *classesDock = nullptr; ResourcesWidget *resourcesDock = nullptr; diff --git a/src/widgets/ExportsWidget.cpp b/src/widgets/ExportsWidget.cpp index 4b6ef8c8..840d5e7a 100644 --- a/src/widgets/ExportsWidget.cpp +++ b/src/widgets/ExportsWidget.cpp @@ -121,8 +121,6 @@ bool ExportsProxyModel::lessThan(const QModelIndex &left, const QModelIndex &rig return leftExp.vaddr < rightExp.vaddr; } - - ExportsWidget::ExportsWidget(MainWindow *main, QAction *action) : CutterDockWidget(main, action), ui(new Ui::ExportsWidget) diff --git a/src/widgets/ExportsWidget.h b/src/widgets/ExportsWidget.h index a853c08f..a317d5eb 100644 --- a/src/widgets/ExportsWidget.h +++ b/src/widgets/ExportsWidget.h @@ -16,12 +16,7 @@ namespace Ui { class ExportsWidget; } - -class MainWindow; -class QTreeWidgetItem; - - -class ExportsModel: public QAbstractListModel +class ExportsModel : public QAbstractListModel { Q_OBJECT @@ -44,8 +39,6 @@ public: void endReloadExports(); }; - - class ExportsProxyModel : public QSortFilterProxyModel { Q_OBJECT @@ -58,8 +51,6 @@ protected: bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; }; - - class ExportsWidget : public CutterDockWidget { Q_OBJECT @@ -83,5 +74,4 @@ private: void setScrollMode(); }; - #endif // EXPORTSWIDGET_H diff --git a/src/widgets/SectionsDock.cpp b/src/widgets/SectionsDock.cpp deleted file mode 100644 index 1d886608..00000000 --- a/src/widgets/SectionsDock.cpp +++ /dev/null @@ -1,71 +0,0 @@ -#include "SectionsDock.h" -#include "ui_SectionsDock.h" - -#include "MainWindow.h" -#include "widgets/SectionsWidget.h" - -#include -#include - - -SectionsDock::SectionsDock(MainWindow *main, QAction *action) : - CutterDockWidget(main, action), - ui(new Ui::SectionsDock), - main(main) -{ - ui->setupUi(this); - - this->sectionsWidget = new SectionsWidget(this->main); - this->setWidget(this->sectionsWidget); - this->sectionsWidget->setContentsMargins(0, 0, 0, 5); - this->setContextMenuPolicy(Qt::CustomContextMenu); - connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), - this, SLOT(showSectionsContextMenu(const QPoint &))); -} - -SectionsDock::~SectionsDock() {} - -void SectionsDock::showSectionsContextMenu(const QPoint &pt) -{ - // Set functions popup menu - QMenu *menu = new QMenu(this); - menu->clear(); - menu->addAction(ui->actionHorizontal); - menu->addAction(ui->actionVertical); - - if (this->sectionsWidget->orientation() == 1) { - ui->actionHorizontal->setChecked(true); - ui->actionVertical->setChecked(false); - } else { - ui->actionVertical->setChecked(true); - ui->actionHorizontal->setChecked(false); - } - - this->setContextMenuPolicy(Qt::CustomContextMenu); - menu->exec(this->mapToGlobal(pt)); - delete menu; -} - -void SectionsDock::resizeEvent(QResizeEvent *event) -{ - if (main->responsive && isVisible()) { - if (event->size().width() >= event->size().height()) { - // Set horizontal view (list) - this->on_actionHorizontal_triggered(); - } else { - // Set vertical view (Tree) - this->on_actionVertical_triggered(); - } - } - QWidget::resizeEvent(event); -} - -void SectionsDock::on_actionVertical_triggered() -{ - this->sectionsWidget->setOrientation(Qt::Vertical); -} - -void SectionsDock::on_actionHorizontal_triggered() -{ - this->sectionsWidget->setOrientation(Qt::Horizontal); -} diff --git a/src/widgets/SectionsDock.h b/src/widgets/SectionsDock.h deleted file mode 100644 index f3746e2e..00000000 --- a/src/widgets/SectionsDock.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef SECTIONSDOCK_H -#define SECTIONSDOCK_H - -#include - -#include "CutterDockWidget.h" - -class MainWindow; -class SectionsWidget; - -namespace Ui { -class SectionsDock; -} - -class SectionsDock : public CutterDockWidget -{ - Q_OBJECT - -public: - explicit SectionsDock(MainWindow *main, QAction *action = nullptr); - ~SectionsDock(); - -protected: - void resizeEvent(QResizeEvent *event) override; - -private slots: - - void showSectionsContextMenu(const QPoint &pt); - - void on_actionVertical_triggered(); - - void on_actionHorizontal_triggered(); - -private: - std::unique_ptr ui; - MainWindow *main; - SectionsWidget *sectionsWidget; -}; - -#endif // SECTIONSDOCK_H diff --git a/src/widgets/SectionsWidget.cpp b/src/widgets/SectionsWidget.cpp index f588bceb..bb876af6 100644 --- a/src/widgets/SectionsWidget.cpp +++ b/src/widgets/SectionsWidget.cpp @@ -1,73 +1,32 @@ -#include "widgets/SectionsWidget.h" -#include "widgets/PieView.h" +#include +#include +#include +#include + +#include "SectionsWidget.h" +#include "ui_SectionsWidget.h" +#include "PieView.h" #include "MainWindow.h" #include "utils/Helpers.h" -#include -#include - -SectionsWidget::SectionsWidget(MainWindow *main, QWidget *parent) : - QSplitter(main), - main(main) +SectionsModel::SectionsModel(QList *sections, QObject *parent) + : QAbstractListModel(parent), + sections(sections) { - Q_UNUSED(parent); - - setupViews(); - setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); - - connect(this->tree, SIGNAL(doubleClicked(const QModelIndex &)), this, - SLOT(onSectionsDoubleClicked(const QModelIndex &))); - - tree->sortByColumn(0, Qt::AscendingOrder); - - connect(Core(), SIGNAL(refreshAll()), this, SLOT(refreshSections())); } -void SectionsWidget::refreshSections() +int SectionsModel::rowCount(const QModelIndex &) const { - tree->clear(); - - int row = 0; - for (auto section : Core()->getAllSections()) { - fillSections(row++, section); - } - - qhelpers::adjustColumns(tree, 0); + return sections->count(); } -void SectionsWidget::setupViews() +int SectionsModel::columnCount(const QModelIndex &) const { - // Table view - this->tree = new QTreeWidget; - this->tree->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - this->tree->setIndentation(10); - - // Setup TreeWidget - this->tree->setColumnCount(4); - QList headers; - headers << tr("Name") << tr("Size") << tr("Address") << tr("End Address"); - this->tree->setHeaderLabels(headers); - - this->tree->setFrameShape(QFrame::NoFrame); - this->tree->setSortingEnabled(true); - - pieChart = new PieView; - pieChart->setFrameShape(QFrame::NoFrame); - pieChart->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - this->addWidget(this->tree); - this->addWidget(pieChart); - this->setStretchFactor(0, 4); - - //this->tree->setModel(model); - pieChart->setModel(this->tree->model()); - - QItemSelectionModel *selectionModel = new QItemSelectionModel(this->tree->model()); - this->tree->setSelectionModel(selectionModel); - pieChart->setSelectionModel(selectionModel); + return SectionsModel::ColumnCount; } -void SectionsWidget::fillSections(int row, const SectionDescription §ion) +QVariant SectionsModel::data(const QModelIndex &index, int role) const { // TODO: create unique colors, e. g. use HSV color space and rotate in H for 360/size static const QList colors = { QColor("#1ABC9C"), //TURQUOISE @@ -83,14 +42,200 @@ void SectionsWidget::fillSections(int row, const SectionDescription §ion) QColor("#95A5A6") //COBCRETE }; - QTreeWidgetItem *tempItem = new QTreeWidgetItem(); - tempItem->setText(0, section.name); - tempItem->setData(1, Qt::DisplayRole, section.size); - tempItem->setTextAlignment(1, Qt::AlignRight | Qt::AlignVCenter); - tempItem->setText(2, RAddressString(section.vaddr)); - tempItem->setText(3, RAddressString(section.vaddr + section.vsize)); - tempItem->setData(0, Qt::DecorationRole, colors[row % colors.size()]); - this->tree->insertTopLevelItem(0, tempItem); + if (index.row() >= sections->count()) + return QVariant(); + + const SectionDescription §ion = sections->at(index.row()); + + switch (role) { + case Qt::DisplayRole: + switch (index.column()) { + case SectionsModel::NameColumn: + return section.name; + case SectionsModel::SizeColumn: + return section.size; + case SectionsModel::AddressColumn: + return RAddressString(section.vaddr); + case SectionsModel::EndAddressColumn: + return RAddressString(section.vaddr + section.size); + default: + return QVariant(); + } + case Qt::DecorationRole: + if (index.column() == 0) + return colors[index.row() % colors.size()]; + return QVariant(); + case SectionsModel::SectionDescriptionRole: + return QVariant::fromValue(section); + default: + return QVariant(); + } +} + +QVariant SectionsModel::headerData(int section, Qt::Orientation, int role) const +{ + switch (role) { + case Qt::DisplayRole: + switch (section) { + case SectionsModel::NameColumn: + return tr("Name"); + case SectionsModel::SizeColumn: + return tr("Size"); + case SectionsModel::AddressColumn: + return tr("Address"); + case SectionsModel::EndAddressColumn: + return tr("EndAddress"); + default: + return QVariant(); + } + default: + return QVariant(); + } +} + +void SectionsModel::beginReloadSections() +{ + beginResetModel(); +} + +void SectionsModel::endReloadSections() +{ + endResetModel(); + // Update PieChart + emit dataChanged(QModelIndex(), QModelIndex()); +} + +SectionsProxyModel::SectionsProxyModel(SectionsModel *sourceModel, QObject *parent) + : QSortFilterProxyModel(parent) +{ + setSourceModel(sourceModel); + setFilterCaseSensitivity(Qt::CaseInsensitive); + setSortCaseSensitivity(Qt::CaseInsensitive); + connect(sourceModel, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector)), + this, SLOT(onSourceModelDataChanged(QModelIndex,QModelIndex,QVector))); +} + +void SectionsProxyModel::onSourceModelDataChanged(const QModelIndex &topLeft, + const QModelIndex &bottomRight, + const QVector &roles) +{ + // Pass the signal further to update PieChart + emit dataChanged(topLeft, bottomRight, roles); +} + +bool SectionsProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const +{ + auto leftSection = left.data(SectionsModel::SectionDescriptionRole).value(); + auto rightSection = right.data(SectionsModel::SectionDescriptionRole).value(); + + switch (left.column()) { + case SectionsModel::NameColumn: + return leftSection.name < rightSection.name; + case SectionsModel::SizeColumn: + return leftSection.size < rightSection.size; + case SectionsModel::AddressColumn: + case SectionsModel::EndAddressColumn: + return leftSection.vaddr < rightSection.vaddr; + default: + break; + } + + return false; +} + +SectionsWidget::SectionsWidget(MainWindow *main, QAction *action) : + CutterDockWidget(main, action), + ui(new Ui::SectionsWidget), + main(main) +{ + ui->setupUi(this); + + sectionsModel = new SectionsModel(§ions, this); + sectionsProxyModel = new SectionsProxyModel(sectionsModel, this); + + setupViews(); + + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + + setContextMenuPolicy(Qt::CustomContextMenu); + connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), + this, SLOT(showSectionsContextMenu(const QPoint &))); + + connect(Core(), SIGNAL(refreshAll()), this, SLOT(refreshSections())); +} + +SectionsWidget::~SectionsWidget() {} + +void SectionsWidget::resizeEvent(QResizeEvent *event) +{ + if (main->responsive && isVisible()) { + if (event->size().width() >= event->size().height()) { + on_actionHorizontal_triggered(); + } else { + on_actionVertical_triggered(); + } + } + QWidget::resizeEvent(event); +} + +void SectionsWidget::showSectionsContextMenu(const QPoint &pt) +{ + // Set functions popup menu + QMenu *menu = new QMenu(this); + menu->clear(); + menu->addAction(ui->actionHorizontal); + menu->addAction(ui->actionVertical); + + if (splitter->orientation() == 1) { + ui->actionHorizontal->setChecked(true); + ui->actionVertical->setChecked(false); + } else { + ui->actionVertical->setChecked(true); + ui->actionHorizontal->setChecked(false); + } + + menu->exec(mapToGlobal(pt)); + delete menu; +} + +void SectionsWidget::refreshSections() +{ + sectionsModel->beginReloadSections(); + sections = Core()->getAllSections(); + sectionsModel->endReloadSections(); + + qhelpers::adjustColumns(sectionsTable, SectionsModel::ColumnCount, 0); +} + +void SectionsWidget::setupViews() +{ + splitter = new QSplitter; + sectionsTable = new QTreeView; + sectionsPieChart = new PieView; + + splitter->addWidget(sectionsTable); + splitter->addWidget(sectionsPieChart); + //splitter->setStretchFactor(0, 4); + + sectionsTable->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + sectionsTable->setIndentation(10); + sectionsTable->setFrameShape(QFrame::NoFrame); + sectionsTable->setSortingEnabled(true); + sectionsTable->sortByColumn(SectionsModel::NameColumn, Qt::AscendingOrder); + connect(sectionsTable, SIGNAL(doubleClicked(const QModelIndex &)), + this, SLOT(onSectionsDoubleClicked(const QModelIndex &))); + + sectionsPieChart->setFrameShape(QFrame::NoFrame); + sectionsPieChart->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + + sectionsTable->setModel(sectionsProxyModel); + sectionsPieChart->setModel(sectionsProxyModel); + + QItemSelectionModel *selectionModel = new QItemSelectionModel(sectionsProxyModel); + sectionsTable->setSelectionModel(selectionModel); + sectionsPieChart->setSelectionModel(selectionModel); + + setWidget(splitter); } void SectionsWidget::onSectionsDoubleClicked(const QModelIndex &index) @@ -98,7 +243,16 @@ void SectionsWidget::onSectionsDoubleClicked(const QModelIndex &index) if (!index.isValid()) return; - QTreeWidgetItem *section = tree->topLevelItem(index.row()); - auto addr = section->text(2); - Core()->seek(addr); + auto section = index.data(SectionsModel::SectionDescriptionRole).value(); + Core()->seek(section.vaddr); +} + +void SectionsWidget::on_actionVertical_triggered() +{ + splitter->setOrientation(Qt::Vertical); +} + +void SectionsWidget::on_actionHorizontal_triggered() +{ + splitter->setOrientation(Qt::Horizontal); } diff --git a/src/widgets/SectionsWidget.h b/src/widgets/SectionsWidget.h index dd2543eb..9823a0bd 100644 --- a/src/widgets/SectionsWidget.h +++ b/src/widgets/SectionsWidget.h @@ -1,41 +1,92 @@ #ifndef SECTIONSWIDGET_H #define SECTIONSWIDGET_H -#include +#include -class MainWindow; -class QTreeWidget; -class QAbstractItemModel; +#include +#include + +#include "Cutter.h" +#include "CutterDockWidget.h" + +class QSplitter; +class QTreeView; class QAbstractItemView; -class QItemSelectionModel; - -struct SectionDescription; +class QResizeEvent; +class MainWindow; namespace Ui { class SectionsWidget; } -class SectionsWidget : public QSplitter +class SectionsModel : public QAbstractListModel +{ + Q_OBJECT + +private: + QList *sections; + +public: + enum Column { NameColumn = 0, SizeColumn, AddressColumn, EndAddressColumn, ColumnCount }; + enum Role { SectionDescriptionRole = Qt::UserRole }; + + SectionsModel(QList *sections, QObject *parent = Q_NULLPTR); + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + + QVariant data(const QModelIndex &index, int role) const; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + + void beginReloadSections(); + void endReloadSections(); +}; + +class SectionsProxyModel : public QSortFilterProxyModel { Q_OBJECT public: - explicit SectionsWidget(MainWindow *main, QWidget *parent = 0); + SectionsProxyModel(SectionsModel *sourceModel, QObject *parent = Q_NULLPTR); + +private slots: + void onSourceModelDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, + const QVector& roles); + +protected: + bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; +}; + +class SectionsWidget : public CutterDockWidget +{ + Q_OBJECT + +public: + explicit SectionsWidget(MainWindow *main, QAction *action = Q_NULLPTR); + ~SectionsWidget(); + +protected: + void resizeEvent(QResizeEvent *event) override; private slots: void refreshSections(); - + void showSectionsContextMenu(const QPoint &pt); + void on_actionVertical_triggered(); + void on_actionHorizontal_triggered(); void onSectionsDoubleClicked(const QModelIndex &index); private: - QAbstractItemView *pieChart; - QItemSelectionModel *selectionModel; - MainWindow *main; - QTreeWidget *tree; + std::unique_ptr ui; + + QList sections; + SectionsModel *sectionsModel; + SectionsProxyModel *sectionsProxyModel; + QSplitter *splitter; + QTreeView *sectionsTable; + QAbstractItemView *sectionsPieChart; + MainWindow *main; void setupViews(); - - void fillSections(int row, const SectionDescription §ion); }; #endif // SECTIONSWIDGET_H diff --git a/src/widgets/SectionsDock.ui b/src/widgets/SectionsWidget.ui similarity index 83% rename from src/widgets/SectionsDock.ui rename to src/widgets/SectionsWidget.ui index 6b7cd7a9..8ac0f4ec 100644 --- a/src/widgets/SectionsDock.ui +++ b/src/widgets/SectionsWidget.ui @@ -1,7 +1,7 @@ - SectionsDock - + SectionsWidget + 0 @@ -13,7 +13,6 @@ Sections - true