cutter/src/widgets/FunctionsWidget.cpp
Nics c4e8a1c178 Small refactor for the widgets of Cutter (#405)
* Small refactor for the widgets of Cutter

This refactor include the following :

* Creation of a new class
Creation of a new class, named CutterWidget, that inherits from QDockWidget and
is used to represent all of the widgets of the main window.
The goal of this class is to regroup all the behaviour shared by the widgets of
Cutter.

For example : in the constructor, instructions corresponding of those
present in the macro **ADD_DOCK** (in MainWindow.cpp) are executed.
This was made because I think that the macro **ADD_DOCK** which is used
to construct the widgets does not take advantage of the object structure.

* Ensure that every widget has a parent
Some widgets were created using the constructor QDockWidget, but using
**nullptr** (default) as argument, thus they haven't got any parent.

The constructor of a CutterWidget takes as argument the MainWindow and an
action (optional) and calls the constructor of QDockWidget with the main
window as argument. This is valid under the assumption that it is mandatory
for every widget to have the main window as a parent.

* Constructors removal
The constructors of some widgets are not used anywhere and does not seem not
fullfill any current usecase. They were removed.

* Renaming CutterWidget to CutterDockWidget
2018-03-16 22:46:57 +01:00

565 lines
17 KiB
C++

#include "FunctionsWidget.h"
#include "ui_FunctionsWidget.h"
#include "MainWindow.h"
#include "utils/Helpers.h"
#include "dialogs/CommentsDialog.h"
#include "dialogs/RenameDialog.h"
#include "dialogs/XrefsDialog.h"
#include <algorithm>
#include <QMenu>
#include <QDebug>
#include <QString>
#include <QResource>
#include <QShortcut>
#include <QJsonObject>
FunctionModel::FunctionModel(QList<FunctionDescription> *functions, QSet<RVA> *importAddresses, ut64 *mainAdress, bool nested, QFont default_font, QFont highlight_font, QObject *parent)
: QAbstractItemModel(parent),
functions(functions),
importAddresses(importAddresses),
mainAdress(mainAdress),
highlightFont(highlight_font),
defaultFont(default_font),
nested(nested),
currentIndex(-1)
{
connect(Core(), SIGNAL(seekChanged(RVA)), this, SLOT(seekChanged(RVA)));
connect(Core(), SIGNAL(functionRenamed(const QString &, const QString &)), this, SLOT(functionRenamed(QString, QString)));
}
QModelIndex FunctionModel::index(int row, int column, const QModelIndex &parent) const
{
if (!parent.isValid())
return createIndex(row, column, (quintptr)0); // root function nodes have id = 0
return createIndex(row, column, (quintptr)(parent.row() + 1)); // sub-nodes have id = function index + 1
}
QModelIndex FunctionModel::parent(const QModelIndex &index) const
{
if (!index.isValid() || index.column() != 0)
return QModelIndex();
if (index.internalId() == 0) // root function node
return QModelIndex();
else // sub-node
return this->index((int)(index.internalId() - 1), 0);
}
int FunctionModel::rowCount(const QModelIndex &parent) const
{
if (!parent.isValid())
return functions->count();
if (nested)
{
if (parent.internalId() == 0)
return 3; // sub-nodes for nested functions
return 0;
}
else
return 0;
}
int FunctionModel::columnCount(const QModelIndex &/*parent*/) const
{
if (nested)
return 1;
else
return ColumnCount;
}
bool FunctionModel::functionIsImport(ut64 addr) const
{
return importAddresses->contains(addr);
}
bool FunctionModel::functionIsMain(ut64 addr) const
{
return *mainAdress == addr;
}
QVariant FunctionModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
int function_index;
bool subnode;
if (index.internalId() != 0) // sub-node
{
function_index = index.parent().row();
subnode = true;
}
else // root function node
{
function_index = index.row();
subnode = false;
}
const FunctionDescription &function = functions->at(function_index);
if (function_index >= functions->count())
return QVariant();
switch (role)
{
case Qt::DisplayRole:
if (nested)
{
if (subnode)
{
switch (index.row())
{
case 0:
return tr("Offset: %1").arg(RAddressString(function.offset));
case 1:
return tr("Size: %1").arg(RSizeString(function.size));
case 2:
return tr("Import: %1").arg(functionIsImport(function.offset) ? tr("true") : tr("false"));
default:
return QVariant();
}
}
else
return function.name;
}
else
{
switch (index.column())
{
case NameColumn:
return function.name;
case SizeColumn:
return function.size;
case OffsetColumn:
return RAddressString(function.offset);
default:
return QVariant();
}
}
case Qt::DecorationRole:
if (importAddresses->contains(function.offset) &&
(nested ? false : index.column() == ImportColumn))
return QIcon(":/img/icons/import_light.svg");
return QVariant();
case Qt::FontRole:
if (currentIndex == function_index)
return highlightFont;
return defaultFont;
case Qt::TextAlignmentRole:
if (index.column() == 1)
return static_cast<int>(Qt::AlignRight | Qt::AlignVCenter);
return static_cast<int>(Qt::AlignLeft | Qt::AlignVCenter);
case Qt::ToolTipRole:
{
QList<QString> info = CutterCore::getInstance()->cmd("afi @ " + function.name).split("\n");
if (info.length() > 2)
{
QString size = info[4].split(" ")[1];
QString complex = info[8].split(" ")[1];
QString bb = info[11].split(" ")[1];
return QString("Summary:\n\n Size: " + size +
"\n Cyclomatic complexity: " + complex +
"\n Basic blocks: " + bb +
"\n\nDisasm preview:\n\n" + CutterCore::getInstance()->cmd("pdi 10 @ " + function.name) +
"\nStrings:\n\n" + CutterCore::getInstance()->cmd("pdsf @ " + function.name));
}
return QVariant();
}
case Qt::ForegroundRole:
if (functionIsImport(function.offset))
return QVariant(ConfigColor("gui.imports"));
if (functionIsMain(function.offset))
return QVariant(ConfigColor("gui.main"));
return QVariant(this->property("color"));
case FunctionDescriptionRole:
return QVariant::fromValue(function);
case IsImportRole:
return importAddresses->contains(function.offset);
default:
return QVariant();
}
}
QVariant FunctionModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role == Qt::DisplayRole && orientation == Qt::Horizontal)
{
if (nested)
{
return tr("Name");
}
else
{
switch (section)
{
case NameColumn:
return tr("Name");
case SizeColumn:
return tr("Size");
case ImportColumn:
return tr("Imp.");
case OffsetColumn:
return tr("Offset");
default:
return QVariant();
}
}
}
return QVariant();
}
void FunctionModel::beginReloadFunctions()
{
beginResetModel();
}
void FunctionModel::endReloadFunctions()
{
updateCurrentIndex();
endResetModel();
}
void FunctionModel::setNested(bool nested)
{
beginResetModel();
this->nested = nested;
updateCurrentIndex();
endResetModel();
}
void FunctionModel::seekChanged(RVA)
{
if (updateCurrentIndex())
{
emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
}
}
bool FunctionModel::updateCurrentIndex()
{
int index = -1;
RVA offset = 0;
RVA seek = Core()->getOffset();
for (int i = 0; i < functions->count(); i++)
{
const FunctionDescription &function = functions->at(i);
if (function.contains(seek)
&& function.offset >= offset)
{
offset = function.offset;
index = i;
}
}
bool changed = currentIndex != index;
currentIndex = index;
return changed;
}
void FunctionModel::functionRenamed(const QString &prev_name, const QString &new_name)
{
for (int i = 0; i < functions->count(); i++)
{
FunctionDescription &function = (*functions)[i];
if (function.name == prev_name)
{
function.name = new_name;
emit dataChanged(index(i, 0), index(i, columnCount() - 1));
}
}
}
FunctionSortFilterProxyModel::FunctionSortFilterProxyModel(FunctionModel *source_model, QObject *parent)
: QSortFilterProxyModel(parent)
{
setSourceModel(source_model);
setFilterCaseSensitivity(Qt::CaseInsensitive);
setSortCaseSensitivity(Qt::CaseInsensitive);
}
bool FunctionSortFilterProxyModel::filterAcceptsRow(int row, const QModelIndex &parent) const
{
QModelIndex index = sourceModel()->index(row, 0, parent);
FunctionDescription function = index.data(FunctionModel::FunctionDescriptionRole).value<FunctionDescription>();
return function.name.contains(filterRegExp());
}
bool FunctionSortFilterProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
{
if (!left.isValid() || !right.isValid())
return false;
if (left.parent().isValid() || right.parent().isValid())
return false;
FunctionDescription left_function = left.data(FunctionModel::FunctionDescriptionRole).value<FunctionDescription>();
FunctionDescription right_function = right.data(FunctionModel::FunctionDescriptionRole).value<FunctionDescription>();
if (static_cast<FunctionModel *>(sourceModel())->isNested())
{
return left_function.name < right_function.name;
}
else
{
switch (left.column())
{
case FunctionModel::OffsetColumn:
return left_function.offset < right_function.offset;
case FunctionModel::SizeColumn:
if (left_function.size != right_function.size)
return left_function.size < right_function.size;
break;
case FunctionModel::ImportColumn:
{
bool left_is_import = left.data(FunctionModel::IsImportRole).toBool();
bool right_is_import = right.data(FunctionModel::IsImportRole).toBool();
if (!left_is_import && right_is_import)
return true;
break;
}
case FunctionModel::NameColumn:
return left_function.name < right_function.name;
default:
return false;
}
return left_function.offset < right_function.offset;
}
}
FunctionsWidget::FunctionsWidget(MainWindow *main, QAction *action) :
CutterDockWidget(main, action),
ui(new Ui::FunctionsWidget)
{
ui->setupUi(this);
// Radare core found in:
this->main = main;
// leave the filter visible by default so users know it exists
//ui->filterLineEdit->setVisible(false);
// Ctrl-F to show/hide the filter entry
QShortcut *search_shortcut = new QShortcut(QKeySequence::Find, this);
connect(search_shortcut, &QShortcut::activated, ui->quickFilterView, &QuickFilterView::showFilter);
search_shortcut->setContext(Qt::WidgetWithChildrenShortcut);
// Esc to clear the filter entry
QShortcut *clear_shortcut = new QShortcut(QKeySequence(Qt::Key_Escape), this);
connect(clear_shortcut, &QShortcut::activated, ui->quickFilterView, &QuickFilterView::clearFilter);
clear_shortcut->setContext(Qt::WidgetWithChildrenShortcut);
QFontInfo font_info = ui->functionsTreeView->fontInfo();
QFont default_font = QFont(font_info.family(), font_info.pointSize());
QFont highlight_font = QFont(font_info.family(), font_info.pointSize(), QFont::Bold);
functionModel = new FunctionModel(&functions, &importAddresses, &mainAdress, false, default_font, highlight_font, this);
functionProxyModel = new FunctionSortFilterProxyModel(functionModel, this);
ui->functionsTreeView->setModel(functionProxyModel);
ui->functionsTreeView->sortByColumn(FunctionModel::NameColumn, Qt::AscendingOrder);
connect(ui->quickFilterView, SIGNAL(filterTextChanged(const QString &)), functionProxyModel, SLOT(setFilterWildcard(const QString &)));
connect(ui->quickFilterView, SIGNAL(filterClosed()), ui->functionsTreeView, SLOT(setFocus()));
setScrollMode();
// Set Functions context menu
connect(ui->functionsTreeView, SIGNAL(customContextMenuRequested(const QPoint &)),
this, SLOT(showFunctionsContextMenu(const QPoint &)));
connect(ui->functionsTreeView, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(onFunctionsDoubleClicked(const QModelIndex &)));
// Use a custom context menu on the dock title bar
//this->title_bar = this->titleBarWidget();
ui->actionHorizontal->setChecked(true);
this->setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
this, SLOT(showTitleContextMenu(const QPoint &)));
connect(Core(), SIGNAL(functionsChanged()), this, SLOT(refreshTree()));
connect(Core(), SIGNAL(refreshAll()), this, SLOT(refreshTree()));
}
FunctionsWidget::~FunctionsWidget() {}
void FunctionsWidget::refreshTree()
{
functionModel->beginReloadFunctions();
functions = CutterCore::getInstance()->getAllFunctions();
importAddresses.clear();
foreach (ImportDescription import, CutterCore::getInstance()->getAllImports())
importAddresses.insert(import.plt);
mainAdress = (ut64)CutterCore::getInstance()->cmdj("iMj").object()["vaddr"].toInt();
functionModel->endReloadFunctions();
// resize offset and size columns
ui->functionsTreeView->resizeColumnToContents(0);
ui->functionsTreeView->resizeColumnToContents(1);
ui->functionsTreeView->resizeColumnToContents(2);
}
void FunctionsWidget::onFunctionsDoubleClicked(const QModelIndex &index)
{
FunctionDescription function = index.data(FunctionModel::FunctionDescriptionRole).value<FunctionDescription>();
Core()->seek(function.offset);
}
void FunctionsWidget::showFunctionsContextMenu(const QPoint &pt)
{
// Set functions popup menu
QMenu *menu = new QMenu(ui->functionsTreeView);
menu->clear();
menu->addAction(ui->actionDisasAdd_comment);
menu->addAction(ui->actionFunctionsRename);
menu->addAction(ui->actionFunctionsUndefine);
menu->addSeparator();
menu->addAction(ui->action_References);
menu->exec(ui->functionsTreeView->mapToGlobal(pt));
delete menu;
}
void FunctionsWidget::on_actionDisasAdd_comment_triggered()
{
// Get selected item in functions tree view
FunctionDescription function = ui->functionsTreeView->selectionModel()->currentIndex().data(FunctionModel::FunctionDescriptionRole).value<FunctionDescription>();
// Create dialog
CommentsDialog *c = new CommentsDialog(this);
if (c->exec())
{
// Get new function name
QString comment = c->getComment();
// Rename function in r2 core
CutterCore::getInstance()->setComment(function.offset, comment);
// Seek to new renamed function
CutterCore::getInstance()->seek(function.offset);
// TODO: Refresh functions tree widget
}
}
void FunctionsWidget::on_actionFunctionsRename_triggered()
{
// Get selected item in functions tree view
FunctionDescription function = ui->functionsTreeView->selectionModel()->currentIndex().data(FunctionModel::FunctionDescriptionRole).value<FunctionDescription>();
// Create dialog
RenameDialog *r = new RenameDialog(this);
// Set function name in dialog
r->setName(function.name);
// If user accepted
if (r->exec())
{
// Get new function name
QString new_name = r->getName();
// Rename function in r2 core
CutterCore::getInstance()->renameFunction(function.name, new_name);
// Seek to new renamed function
CutterCore::getInstance()->seek(function.offset);
}
}
void FunctionsWidget::on_actionFunctionsUndefine_triggered()
{
FunctionDescription function = ui->functionsTreeView->selectionModel()->currentIndex().data(FunctionModel::FunctionDescriptionRole).value<FunctionDescription>();
Core()->delFunction(function.offset);
}
void FunctionsWidget::on_action_References_triggered()
{
// Get selected item in functions tree view
FunctionDescription function = ui->functionsTreeView->selectionModel()->currentIndex().data(FunctionModel::FunctionDescriptionRole).value<FunctionDescription>();
XrefsDialog *x = new XrefsDialog(this);
x->fillRefsForAddress(function.offset, function.name, true);
x->exec();
}
void FunctionsWidget::showTitleContextMenu(const QPoint &pt)
{
// Set functions popup menu
QMenu *menu = new QMenu(this);
menu->clear();
menu->addAction(ui->actionHorizontal);
menu->addAction(ui->actionVertical);
if (!functionModel->isNested())
{
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 FunctionsWidget::on_actionHorizontal_triggered()
{
functionModel->setNested(false);
ui->functionsTreeView->setIndentation(8);
}
void FunctionsWidget::on_actionVertical_triggered()
{
functionModel->setNested(true);
ui->functionsTreeView->setIndentation(20);
}
void FunctionsWidget::resizeEvent(QResizeEvent *event)
{
if (main->responsive && isVisible())
{
if (event->size().width() >= event->size().height())
{
// Set horizontal view (list)
on_actionHorizontal_triggered();
}
else
{
// Set vertical view (Tree)
on_actionVertical_triggered();
}
}
QDockWidget::resizeEvent(event);
}
void FunctionsWidget::setScrollMode()
{
qhelpers::setVerticalScrollMode(ui->functionsTreeView);
}