2017-10-02 16:18:40 +00:00
|
|
|
#include "FunctionsWidget.h"
|
2019-08-19 13:35:25 +00:00
|
|
|
#include "ui_ListDockWidget.h"
|
2017-03-29 10:18:37 +00:00
|
|
|
|
2019-02-22 16:50:45 +00:00
|
|
|
#include "core/MainWindow.h"
|
2018-10-17 07:55:53 +00:00
|
|
|
#include "common/Helpers.h"
|
|
|
|
#include "common/FunctionsTask.h"
|
|
|
|
#include "common/TempConfig.h"
|
2019-08-19 13:35:25 +00:00
|
|
|
#include "menus/AddressableItemContextMenu.h"
|
2017-03-29 10:18:37 +00:00
|
|
|
|
2017-04-28 13:09:40 +00:00
|
|
|
#include <algorithm>
|
2017-03-29 10:18:37 +00:00
|
|
|
#include <QMenu>
|
|
|
|
#include <QDebug>
|
|
|
|
#include <QString>
|
2017-04-28 13:09:40 +00:00
|
|
|
#include <QResource>
|
2017-05-18 10:18:17 +00:00
|
|
|
#include <QShortcut>
|
2018-10-15 19:54:52 +00:00
|
|
|
#include <QJsonArray>
|
2018-02-12 08:59:45 +00:00
|
|
|
#include <QJsonObject>
|
2020-06-14 08:52:48 +00:00
|
|
|
#include <QInputDialog>
|
2017-04-28 13:09:40 +00:00
|
|
|
|
2018-10-15 19:54:52 +00:00
|
|
|
namespace {
|
|
|
|
|
|
|
|
static const int kMaxTooltipWidth = 400;
|
|
|
|
static const int kMaxTooltipDisasmPreviewLines = 10;
|
|
|
|
static const int kMaxTooltipHighlightsLines = 5;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2018-03-21 20:32:32 +00:00
|
|
|
FunctionModel::FunctionModel(QList<FunctionDescription> *functions, QSet<RVA> *importAddresses,
|
|
|
|
ut64 *mainAdress, bool nested, QFont default_font, QFont highlight_font, QObject *parent)
|
2019-08-19 13:35:25 +00:00
|
|
|
: AddressableItemModel<>(parent),
|
2017-04-28 13:38:01 +00:00
|
|
|
functions(functions),
|
2018-02-09 14:03:03 +00:00
|
|
|
importAddresses(importAddresses),
|
2018-02-12 08:59:45 +00:00
|
|
|
mainAdress(mainAdress),
|
2018-02-09 14:03:03 +00:00
|
|
|
highlightFont(highlight_font),
|
|
|
|
defaultFont(default_font),
|
2017-04-28 13:38:01 +00:00
|
|
|
nested(nested),
|
2018-02-09 14:03:03 +00:00
|
|
|
currentIndex(-1)
|
2017-04-28 13:09:40 +00:00
|
|
|
|
2017-04-28 13:38:01 +00:00
|
|
|
{
|
2020-08-03 09:13:39 +00:00
|
|
|
connect(Core(), &CutterCore::seekChanged, this, &FunctionModel::seekChanged);
|
|
|
|
connect(Core(), &CutterCore::functionRenamed,
|
|
|
|
this, &FunctionModel::functionRenamed);
|
2017-04-28 13:09:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
QModelIndex FunctionModel::index(int row, int column, const QModelIndex &parent) const
|
|
|
|
{
|
2017-04-28 13:38:01 +00:00
|
|
|
if (!parent.isValid())
|
2017-04-28 13:09:40 +00:00
|
|
|
return createIndex(row, column, (quintptr)0); // root function nodes have id = 0
|
|
|
|
|
2018-03-21 20:32:32 +00:00
|
|
|
return createIndex(row, column,
|
|
|
|
(quintptr)(parent.row() + 1)); // sub-nodes have id = function index + 1
|
2017-04-28 13:09:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
QModelIndex FunctionModel::parent(const QModelIndex &index) const
|
|
|
|
{
|
2017-04-28 13:38:01 +00:00
|
|
|
if (!index.isValid() || index.column() != 0)
|
2017-04-28 13:09:40 +00:00
|
|
|
return QModelIndex();
|
|
|
|
|
2017-04-28 13:38:01 +00:00
|
|
|
if (index.internalId() == 0) // root function node
|
2017-04-28 13:09:40 +00:00
|
|
|
return QModelIndex();
|
|
|
|
else // sub-node
|
2017-04-28 13:38:01 +00:00
|
|
|
return this->index((int)(index.internalId() - 1), 0);
|
2017-04-28 13:09:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int FunctionModel::rowCount(const QModelIndex &parent) const
|
|
|
|
{
|
2017-04-28 13:38:01 +00:00
|
|
|
if (!parent.isValid())
|
2017-04-28 13:09:40 +00:00
|
|
|
return functions->count();
|
|
|
|
|
2018-03-21 20:32:32 +00:00
|
|
|
if (nested) {
|
2017-04-28 13:38:01 +00:00
|
|
|
if (parent.internalId() == 0)
|
2018-10-11 12:05:47 +00:00
|
|
|
return ColumnCount - 1; // sub-nodes for nested functions
|
2017-04-28 13:09:40 +00:00
|
|
|
return 0;
|
2018-03-21 20:32:32 +00:00
|
|
|
} else
|
2017-04-28 13:09:40 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-04-28 13:38:01 +00:00
|
|
|
int FunctionModel::columnCount(const QModelIndex &/*parent*/) const
|
2017-04-28 13:09:40 +00:00
|
|
|
{
|
2017-04-28 13:38:01 +00:00
|
|
|
if (nested)
|
2017-04-28 13:09:40 +00:00
|
|
|
return 1;
|
|
|
|
else
|
2017-11-04 15:28:02 +00:00
|
|
|
return ColumnCount;
|
2017-04-28 13:09:40 +00:00
|
|
|
}
|
|
|
|
|
2017-12-20 15:40:46 +00:00
|
|
|
bool FunctionModel::functionIsImport(ut64 addr) const
|
|
|
|
{
|
2018-02-09 14:03:03 +00:00
|
|
|
return importAddresses->contains(addr);
|
2017-12-20 15:40:46 +00:00
|
|
|
}
|
|
|
|
|
2018-02-12 08:59:45 +00:00
|
|
|
bool FunctionModel::functionIsMain(ut64 addr) const
|
|
|
|
{
|
|
|
|
return *mainAdress == addr;
|
|
|
|
}
|
2017-04-28 13:09:40 +00:00
|
|
|
|
|
|
|
QVariant FunctionModel::data(const QModelIndex &index, int role) const
|
|
|
|
{
|
2017-04-28 13:38:01 +00:00
|
|
|
if (!index.isValid())
|
2017-04-28 13:09:40 +00:00
|
|
|
return QVariant();
|
|
|
|
|
|
|
|
int function_index;
|
|
|
|
bool subnode;
|
2018-03-21 20:32:32 +00:00
|
|
|
if (index.internalId() != 0) { // sub-node
|
2017-04-28 13:09:40 +00:00
|
|
|
function_index = index.parent().row();
|
|
|
|
subnode = true;
|
2018-03-21 20:32:32 +00:00
|
|
|
} else { // root function node
|
2017-04-28 13:09:40 +00:00
|
|
|
function_index = index.row();
|
|
|
|
subnode = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const FunctionDescription &function = functions->at(function_index);
|
|
|
|
|
2017-04-28 13:38:01 +00:00
|
|
|
if (function_index >= functions->count())
|
2017-04-28 13:09:40 +00:00
|
|
|
return QVariant();
|
|
|
|
|
2018-03-21 20:32:32 +00:00
|
|
|
switch (role) {
|
2017-04-28 13:38:01 +00:00
|
|
|
case Qt::DisplayRole:
|
2018-03-21 20:32:32 +00:00
|
|
|
if (nested) {
|
|
|
|
if (subnode) {
|
|
|
|
switch (index.row()) {
|
2017-04-28 13:38:01 +00:00
|
|
|
case 0:
|
|
|
|
return tr("Offset: %1").arg(RAddressString(function.offset));
|
|
|
|
case 1:
|
2020-01-15 19:19:24 +00:00
|
|
|
return tr("Size: %1").arg(RSizeString(function.linearSize));
|
2017-04-28 13:38:01 +00:00
|
|
|
case 2:
|
2017-12-20 15:40:46 +00:00
|
|
|
return tr("Import: %1").arg(functionIsImport(function.offset) ? tr("true") : tr("false"));
|
2018-04-10 18:34:11 +00:00
|
|
|
case 3:
|
|
|
|
return tr("Nargs: %1").arg(RSizeString(function.nargs));
|
|
|
|
case 4:
|
|
|
|
return tr("Nbbs: %1").arg(RSizeString(function.nbbs));
|
|
|
|
case 5:
|
|
|
|
return tr("Nlocals: %1").arg(RSizeString(function.nlocals));
|
|
|
|
case 6:
|
|
|
|
return tr("Call type: %1").arg(function.calltype);
|
2019-05-01 11:34:15 +00:00
|
|
|
case 7:
|
2018-10-10 06:41:16 +00:00
|
|
|
return tr("Edges: %1").arg(function.edges);
|
2019-05-01 11:34:15 +00:00
|
|
|
case 8:
|
2018-10-10 06:41:16 +00:00
|
|
|
return tr("StackFrame: %1").arg(function.stackframe);
|
2017-04-28 13:38:01 +00:00
|
|
|
default:
|
|
|
|
return QVariant();
|
2017-04-28 13:09:40 +00:00
|
|
|
}
|
2018-03-21 20:32:32 +00:00
|
|
|
} else
|
2017-04-28 13:38:01 +00:00
|
|
|
return function.name;
|
2018-03-21 20:32:32 +00:00
|
|
|
} else {
|
|
|
|
switch (index.column()) {
|
2017-11-04 15:28:02 +00:00
|
|
|
case NameColumn:
|
2017-10-13 13:53:23 +00:00
|
|
|
return function.name;
|
2017-11-04 15:28:02 +00:00
|
|
|
case SizeColumn:
|
2020-01-15 19:19:24 +00:00
|
|
|
return QString::number(function.linearSize);
|
2017-11-04 15:28:02 +00:00
|
|
|
case OffsetColumn:
|
2017-10-13 13:53:23 +00:00
|
|
|
return RAddressString(function.offset);
|
2018-04-10 18:34:11 +00:00
|
|
|
case NargsColumn:
|
2019-08-19 13:35:25 +00:00
|
|
|
return QString::number(function.nargs);
|
2018-04-10 18:34:11 +00:00
|
|
|
case NlocalsColumn:
|
2019-08-19 13:35:25 +00:00
|
|
|
return QString::number(function.nlocals);
|
2019-05-01 11:34:15 +00:00
|
|
|
case NbbsColumn:
|
2019-08-19 13:35:25 +00:00
|
|
|
return QString::number(function.nbbs);
|
2018-04-10 18:34:11 +00:00
|
|
|
case CalltypeColumn:
|
|
|
|
return function.calltype;
|
2018-10-10 06:41:16 +00:00
|
|
|
case EdgesColumn:
|
2019-08-19 13:35:25 +00:00
|
|
|
return QString::number(function.edges);
|
2018-10-10 06:41:16 +00:00
|
|
|
case FrameColumn:
|
2019-08-19 13:35:25 +00:00
|
|
|
return QString::number(function.stackframe);
|
2017-04-28 13:38:01 +00:00
|
|
|
default:
|
|
|
|
return QVariant();
|
2017-04-28 13:09:40 +00:00
|
|
|
}
|
2017-04-28 13:38:01 +00:00
|
|
|
}
|
2017-04-28 13:09:40 +00:00
|
|
|
|
2017-04-28 13:38:01 +00:00
|
|
|
case Qt::DecorationRole:
|
2018-02-09 14:03:03 +00:00
|
|
|
if (importAddresses->contains(function.offset) &&
|
2019-03-23 10:54:34 +00:00
|
|
|
(nested ? false : index.column() == ImportColumn)) {
|
|
|
|
const static QIcon importIcon(":/img/icons/import_light.svg");
|
|
|
|
return importIcon;
|
|
|
|
}
|
2017-04-28 13:38:01 +00:00
|
|
|
return QVariant();
|
2017-04-28 13:09:40 +00:00
|
|
|
|
2017-04-28 13:38:01 +00:00
|
|
|
case Qt::FontRole:
|
2018-02-09 14:03:03 +00:00
|
|
|
if (currentIndex == function_index)
|
|
|
|
return highlightFont;
|
|
|
|
return defaultFont;
|
2017-04-28 13:09:40 +00:00
|
|
|
|
2018-02-28 20:12:51 +00:00
|
|
|
case Qt::TextAlignmentRole:
|
|
|
|
if (index.column() == 1)
|
|
|
|
return static_cast<int>(Qt::AlignRight | Qt::AlignVCenter);
|
|
|
|
return static_cast<int>(Qt::AlignLeft | Qt::AlignVCenter);
|
|
|
|
|
2018-03-21 20:32:32 +00:00
|
|
|
case Qt::ToolTipRole: {
|
2018-10-15 19:54:52 +00:00
|
|
|
|
2019-08-19 13:35:25 +00:00
|
|
|
QStringList disasmPreview = Core()->getDisassemblyPreview(function.offset,
|
|
|
|
kMaxTooltipDisasmPreviewLines);
|
2019-03-23 10:54:34 +00:00
|
|
|
const QStringList &summary = Core()->cmdList(QString("pdsf @ %1").arg(function.offset));
|
2019-01-08 12:23:17 +00:00
|
|
|
const QFont &fnt = Config()->getFont();
|
|
|
|
QFontMetrics fm{ fnt };
|
2018-10-15 19:54:52 +00:00
|
|
|
|
2019-01-09 05:45:07 +00:00
|
|
|
// elide long strings using current disasm font metrics
|
2019-01-08 12:23:17 +00:00
|
|
|
QStringList highlights;
|
|
|
|
for (const QString &s : summary) {
|
|
|
|
highlights << fm.elidedText(s, Qt::ElideRight, kMaxTooltipWidth);
|
|
|
|
if (highlights.length() > kMaxTooltipHighlightsLines) {
|
|
|
|
highlights << "...";
|
|
|
|
break;
|
2018-10-15 19:54:52 +00:00
|
|
|
}
|
2017-04-28 13:09:40 +00:00
|
|
|
}
|
2019-01-09 05:45:07 +00:00
|
|
|
if (disasmPreview.isEmpty() && highlights.isEmpty())
|
|
|
|
return QVariant();
|
|
|
|
|
2019-08-19 13:35:25 +00:00
|
|
|
QString toolTipContent =
|
|
|
|
QString("<html><div style=\"font-family: %1; font-size: %2pt; white-space: nowrap;\">")
|
|
|
|
.arg(fnt.family())
|
|
|
|
.arg(qMax(6, fnt.pointSize() -
|
|
|
|
1)); // slightly decrease font size, to keep more text in the same box
|
2019-01-09 05:45:07 +00:00
|
|
|
|
|
|
|
if (!disasmPreview.isEmpty())
|
2019-08-19 13:35:25 +00:00
|
|
|
toolTipContent +=
|
|
|
|
tr("<div style=\"margin-bottom: 10px;\"><strong>Disassembly preview</strong>:<br>%1</div>")
|
2019-01-08 12:23:17 +00:00
|
|
|
.arg(disasmPreview.join("<br>"));
|
|
|
|
|
|
|
|
if (!highlights.isEmpty()) {
|
2019-01-09 05:45:07 +00:00
|
|
|
toolTipContent += tr("<div><strong>Highlights</strong>:<br>%1</div>")
|
2019-08-19 13:35:25 +00:00
|
|
|
.arg(highlights.join(QLatin1Char('\n')).toHtmlEscaped().replace(QLatin1Char('\n'), "<br>"));
|
2019-01-08 12:23:17 +00:00
|
|
|
}
|
|
|
|
toolTipContent += "</div></html>";
|
|
|
|
return toolTipContent;
|
2017-04-28 13:38:01 +00:00
|
|
|
}
|
2017-04-28 13:09:40 +00:00
|
|
|
|
2017-12-20 15:40:46 +00:00
|
|
|
case Qt::ForegroundRole:
|
|
|
|
if (functionIsImport(function.offset))
|
|
|
|
return QVariant(ConfigColor("gui.imports"));
|
2018-02-12 08:59:45 +00:00
|
|
|
if (functionIsMain(function.offset))
|
|
|
|
return QVariant(ConfigColor("gui.main"));
|
2018-01-09 11:03:07 +00:00
|
|
|
return QVariant(this->property("color"));
|
2017-12-20 15:40:46 +00:00
|
|
|
|
2017-04-28 13:38:01 +00:00
|
|
|
case FunctionDescriptionRole:
|
|
|
|
return QVariant::fromValue(function);
|
2017-04-28 13:09:40 +00:00
|
|
|
|
2017-04-28 13:38:01 +00:00
|
|
|
case IsImportRole:
|
2018-02-09 14:03:03 +00:00
|
|
|
return importAddresses->contains(function.offset);
|
2017-04-28 13:09:40 +00:00
|
|
|
|
2017-04-28 13:38:01 +00:00
|
|
|
default:
|
|
|
|
return QVariant();
|
2017-04-28 13:09:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QVariant FunctionModel::headerData(int section, Qt::Orientation orientation, int role) const
|
|
|
|
{
|
2018-03-21 20:32:32 +00:00
|
|
|
if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
|
|
|
|
if (nested) {
|
2017-04-28 13:09:40 +00:00
|
|
|
return tr("Name");
|
2018-03-21 20:32:32 +00:00
|
|
|
} else {
|
|
|
|
switch (section) {
|
2017-11-04 15:28:02 +00:00
|
|
|
case NameColumn:
|
2017-10-13 13:53:23 +00:00
|
|
|
return tr("Name");
|
2017-11-04 15:28:02 +00:00
|
|
|
case SizeColumn:
|
2017-04-28 13:38:01 +00:00
|
|
|
return tr("Size");
|
2017-11-04 15:28:02 +00:00
|
|
|
case ImportColumn:
|
2017-04-28 13:38:01 +00:00
|
|
|
return tr("Imp.");
|
2017-11-04 15:28:02 +00:00
|
|
|
case OffsetColumn:
|
2017-10-13 13:53:23 +00:00
|
|
|
return tr("Offset");
|
2018-04-10 18:34:11 +00:00
|
|
|
case NargsColumn:
|
|
|
|
return tr("Nargs");
|
|
|
|
case NlocalsColumn:
|
|
|
|
return tr("Nlocals");
|
2019-05-01 11:34:15 +00:00
|
|
|
case NbbsColumn:
|
|
|
|
return tr("Nbbs");
|
2018-04-10 18:34:11 +00:00
|
|
|
case CalltypeColumn:
|
|
|
|
return tr("Call type");
|
2018-10-10 06:41:16 +00:00
|
|
|
case EdgesColumn:
|
|
|
|
return tr("Edges");
|
|
|
|
case FrameColumn:
|
|
|
|
return tr("StackFrame");
|
2017-04-28 13:38:01 +00:00
|
|
|
default:
|
|
|
|
return QVariant();
|
2017-04-28 13:09:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return QVariant();
|
|
|
|
}
|
|
|
|
|
2017-12-21 19:51:19 +00:00
|
|
|
void FunctionModel::setNested(bool nested)
|
|
|
|
{
|
|
|
|
beginResetModel();
|
|
|
|
this->nested = nested;
|
|
|
|
updateCurrentIndex();
|
|
|
|
endResetModel();
|
|
|
|
}
|
|
|
|
|
2019-08-19 13:35:25 +00:00
|
|
|
RVA FunctionModel::address(const QModelIndex &index) const
|
|
|
|
{
|
|
|
|
auto function = data(index, FunctionDescriptionRole).value<FunctionDescription>();
|
|
|
|
return function.offset;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString FunctionModel::name(const QModelIndex &index) const
|
|
|
|
{
|
|
|
|
auto function = data(index, FunctionDescriptionRole).value<FunctionDescription>();
|
|
|
|
return function.name;
|
|
|
|
}
|
|
|
|
|
2017-11-07 13:11:33 +00:00
|
|
|
void FunctionModel::seekChanged(RVA)
|
2017-04-28 13:09:40 +00:00
|
|
|
{
|
2018-05-25 18:55:05 +00:00
|
|
|
int previousIndex = currentIndex;
|
2018-03-21 20:32:32 +00:00
|
|
|
if (updateCurrentIndex()) {
|
2018-05-25 18:55:05 +00:00
|
|
|
if (previousIndex >= 0) {
|
|
|
|
emit dataChanged(index(previousIndex, 0), index(previousIndex, columnCount() - 1));
|
|
|
|
}
|
|
|
|
if (currentIndex >= 0) {
|
|
|
|
emit dataChanged(index(currentIndex, 0), index(currentIndex, columnCount() - 1));
|
|
|
|
}
|
2017-11-07 13:11:33 +00:00
|
|
|
}
|
2017-04-28 13:09:40 +00:00
|
|
|
}
|
|
|
|
|
2017-11-07 13:11:33 +00:00
|
|
|
bool FunctionModel::updateCurrentIndex()
|
2017-04-28 13:09:40 +00:00
|
|
|
{
|
|
|
|
int index = -1;
|
|
|
|
RVA offset = 0;
|
|
|
|
|
2017-11-07 13:11:33 +00:00
|
|
|
RVA seek = Core()->getOffset();
|
|
|
|
|
2018-03-21 20:32:32 +00:00
|
|
|
for (int i = 0; i < functions->count(); i++) {
|
2017-04-28 13:09:40 +00:00
|
|
|
const FunctionDescription &function = functions->at(i);
|
|
|
|
|
2017-11-07 13:11:33 +00:00
|
|
|
if (function.contains(seek)
|
2018-03-21 20:32:32 +00:00
|
|
|
&& function.offset >= offset) {
|
2017-04-28 13:09:40 +00:00
|
|
|
offset = function.offset;
|
|
|
|
index = i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-09 14:03:03 +00:00
|
|
|
bool changed = currentIndex != index;
|
2017-11-07 13:11:33 +00:00
|
|
|
|
2018-02-09 14:03:03 +00:00
|
|
|
currentIndex = index;
|
2017-11-07 13:11:33 +00:00
|
|
|
|
|
|
|
return changed;
|
2017-04-28 13:09:40 +00:00
|
|
|
}
|
|
|
|
|
2020-07-16 08:56:38 +00:00
|
|
|
void FunctionModel::functionRenamed(const RVA offset, const QString &new_name)
|
2017-04-28 13:09:40 +00:00
|
|
|
{
|
2018-03-21 20:32:32 +00:00
|
|
|
for (int i = 0; i < functions->count(); i++) {
|
2017-04-28 13:09:40 +00:00
|
|
|
FunctionDescription &function = (*functions)[i];
|
2020-07-16 08:56:38 +00:00
|
|
|
if (function.offset == offset) {
|
2017-04-28 13:09:40 +00:00
|
|
|
function.name = new_name;
|
2017-04-28 13:38:01 +00:00
|
|
|
emit dataChanged(index(i, 0), index(i, columnCount() - 1));
|
2017-04-28 13:09:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-21 20:32:32 +00:00
|
|
|
FunctionSortFilterProxyModel::FunctionSortFilterProxyModel(FunctionModel *source_model,
|
|
|
|
QObject *parent)
|
2019-08-19 13:35:25 +00:00
|
|
|
: AddressableFilterProxyModel(source_model, parent)
|
2017-04-28 13:09:40 +00:00
|
|
|
{
|
|
|
|
setFilterCaseSensitivity(Qt::CaseInsensitive);
|
|
|
|
setSortCaseSensitivity(Qt::CaseInsensitive);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool FunctionSortFilterProxyModel::filterAcceptsRow(int row, const QModelIndex &parent) const
|
|
|
|
{
|
|
|
|
QModelIndex index = sourceModel()->index(row, 0, parent);
|
2018-03-21 20:32:32 +00:00
|
|
|
FunctionDescription function = index.data(
|
|
|
|
FunctionModel::FunctionDescriptionRole).value<FunctionDescription>();
|
2017-04-28 13:09:40 +00:00
|
|
|
return function.name.contains(filterRegExp());
|
|
|
|
}
|
|
|
|
|
|
|
|
bool FunctionSortFilterProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
|
|
|
|
{
|
2017-04-28 13:38:01 +00:00
|
|
|
if (!left.isValid() || !right.isValid())
|
2017-04-28 13:09:40 +00:00
|
|
|
return false;
|
|
|
|
|
2017-04-28 13:38:01 +00:00
|
|
|
if (left.parent().isValid() || right.parent().isValid())
|
2017-04-28 13:09:40 +00:00
|
|
|
return false;
|
|
|
|
|
2018-03-21 20:32:32 +00:00
|
|
|
FunctionDescription left_function = left.data(
|
|
|
|
FunctionModel::FunctionDescriptionRole).value<FunctionDescription>();
|
|
|
|
FunctionDescription right_function = right.data(
|
|
|
|
FunctionModel::FunctionDescriptionRole).value<FunctionDescription>();
|
2017-04-28 13:09:40 +00:00
|
|
|
|
|
|
|
|
2018-03-21 20:32:32 +00:00
|
|
|
if (static_cast<FunctionModel *>(sourceModel())->isNested()) {
|
2017-04-28 13:09:40 +00:00
|
|
|
return left_function.name < right_function.name;
|
2018-03-21 20:32:32 +00:00
|
|
|
} else {
|
|
|
|
switch (left.column()) {
|
2017-11-04 15:28:02 +00:00
|
|
|
case FunctionModel::OffsetColumn:
|
2017-04-28 13:38:01 +00:00
|
|
|
return left_function.offset < right_function.offset;
|
2017-11-04 15:28:02 +00:00
|
|
|
case FunctionModel::SizeColumn:
|
2020-01-15 19:19:24 +00:00
|
|
|
if (left_function.linearSize != right_function.linearSize)
|
|
|
|
return left_function.linearSize < right_function.linearSize;
|
2017-04-28 13:38:01 +00:00
|
|
|
break;
|
2018-03-21 20:32:32 +00:00
|
|
|
case FunctionModel::ImportColumn: {
|
2017-04-28 13:38:01 +00:00
|
|
|
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;
|
|
|
|
}
|
2017-11-04 15:28:02 +00:00
|
|
|
case FunctionModel::NameColumn:
|
2017-04-28 13:38:01 +00:00
|
|
|
return left_function.name < right_function.name;
|
2018-04-10 18:34:11 +00:00
|
|
|
case FunctionModel::NargsColumn:
|
2018-09-30 20:00:53 +00:00
|
|
|
if (left_function.nargs != right_function.nargs)
|
|
|
|
return left_function.nargs < right_function.nargs;
|
|
|
|
break;
|
2018-04-10 18:34:11 +00:00
|
|
|
case FunctionModel::NlocalsColumn:
|
2018-09-30 20:00:53 +00:00
|
|
|
if (left_function.nlocals != right_function.nlocals)
|
|
|
|
return left_function.nlocals < right_function.nlocals;
|
|
|
|
break;
|
2019-05-01 11:34:15 +00:00
|
|
|
case FunctionModel::NbbsColumn:
|
|
|
|
if (left_function.nbbs != right_function.nbbs)
|
|
|
|
return left_function.nbbs < right_function.nbbs;
|
2018-09-30 20:00:53 +00:00
|
|
|
break;
|
2018-04-10 18:34:11 +00:00
|
|
|
case FunctionModel::CalltypeColumn:
|
2018-09-30 20:00:53 +00:00
|
|
|
return left_function.calltype < right_function.calltype;
|
2018-10-10 06:41:16 +00:00
|
|
|
case FunctionModel::EdgesColumn:
|
|
|
|
if (left_function.edges != right_function.edges)
|
|
|
|
return left_function.edges < right_function.edges;
|
|
|
|
break;
|
|
|
|
case FunctionModel::FrameColumn:
|
|
|
|
if (left_function.stackframe != right_function.stackframe)
|
|
|
|
return left_function.stackframe < right_function.stackframe;
|
|
|
|
break;
|
2017-04-28 13:38:01 +00:00
|
|
|
default:
|
|
|
|
return false;
|
2017-04-28 13:09:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return left_function.offset < right_function.offset;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-22 11:49:34 +00:00
|
|
|
FunctionsWidget::FunctionsWidget(MainWindow *main) :
|
|
|
|
ListDockWidget(main),
|
2019-08-19 13:35:25 +00:00
|
|
|
actionRename(tr("Rename"), this),
|
|
|
|
actionUndefine(tr("Undefine"), this),
|
|
|
|
actionHorizontal(tr("Horizontal"), this),
|
|
|
|
actionVertical(tr("Vertical"), this)
|
2017-03-29 10:18:37 +00:00
|
|
|
{
|
2019-08-19 13:35:25 +00:00
|
|
|
setWindowTitle(tr("Functions"));
|
|
|
|
setObjectName("FunctionsWidget");
|
2017-03-29 10:18:37 +00:00
|
|
|
|
2019-04-27 17:58:44 +00:00
|
|
|
setTooltipStylesheet();
|
2020-08-03 09:13:39 +00:00
|
|
|
connect(Config(), &Configuration::colorsUpdated, this, &FunctionsWidget::setTooltipStylesheet);
|
2017-04-28 13:09:40 +00:00
|
|
|
|
2019-08-19 13:35:25 +00:00
|
|
|
QFontInfo font_info = ui->treeView->fontInfo();
|
2017-04-28 13:09:40 +00:00
|
|
|
QFont default_font = QFont(font_info.family(), font_info.pointSize());
|
|
|
|
QFont highlight_font = QFont(font_info.family(), font_info.pointSize(), QFont::Bold);
|
|
|
|
|
2018-03-21 20:32:32 +00:00
|
|
|
functionModel = new FunctionModel(&functions, &importAddresses, &mainAdress, false, default_font,
|
|
|
|
highlight_font, this);
|
2018-02-09 14:03:03 +00:00
|
|
|
functionProxyModel = new FunctionSortFilterProxyModel(functionModel, this);
|
2019-08-19 13:35:25 +00:00
|
|
|
setModels(functionProxyModel);
|
|
|
|
ui->treeView->sortByColumn(FunctionModel::NameColumn, Qt::AscendingOrder);
|
|
|
|
|
|
|
|
|
|
|
|
titleContextMenu = new QMenu(this);
|
|
|
|
auto viewTypeGroup = new QActionGroup(titleContextMenu);
|
|
|
|
actionHorizontal.setCheckable(true);
|
|
|
|
actionHorizontal.setActionGroup(viewTypeGroup);
|
|
|
|
connect(&actionHorizontal, &QAction::toggled, this, &FunctionsWidget::onActionHorizontalToggled);
|
|
|
|
actionVertical.setCheckable(true);
|
|
|
|
actionVertical.setActionGroup(viewTypeGroup);
|
|
|
|
connect(&actionVertical, &QAction::toggled, this, &FunctionsWidget::onActionVerticalToggled);
|
|
|
|
titleContextMenu->addActions(viewTypeGroup->actions());
|
|
|
|
|
|
|
|
actionRename.setShortcut({Qt::Key_N});
|
|
|
|
actionRename.setShortcutContext(Qt::ShortcutContext::WidgetWithChildrenShortcut);
|
|
|
|
connect(&actionRename, &QAction::triggered, this,
|
|
|
|
&FunctionsWidget::onActionFunctionsRenameTriggered);
|
|
|
|
connect(&actionUndefine, &QAction::triggered, this,
|
|
|
|
&FunctionsWidget::onActionFunctionsUndefineTriggered);
|
|
|
|
|
2019-09-01 21:30:25 +00:00
|
|
|
auto itemConextMenu = ui->treeView->getItemContextMenu();
|
2019-08-19 13:35:25 +00:00
|
|
|
itemConextMenu->addSeparator();
|
|
|
|
itemConextMenu->addAction(&actionRename);
|
|
|
|
itemConextMenu->addAction(&actionUndefine);
|
|
|
|
itemConextMenu->setWholeFunction(true);
|
|
|
|
|
|
|
|
addActions(itemConextMenu->actions());
|
2017-03-29 10:18:37 +00:00
|
|
|
|
|
|
|
// Use a custom context menu on the dock title bar
|
2019-08-19 13:35:25 +00:00
|
|
|
actionHorizontal.setChecked(true);
|
2017-03-29 10:18:37 +00:00
|
|
|
this->setContextMenuPolicy(Qt::CustomContextMenu);
|
2020-08-03 09:13:39 +00:00
|
|
|
connect(this, &QWidget::customContextMenuRequested,
|
|
|
|
this, &FunctionsWidget::showTitleContextMenu);
|
2017-03-29 10:18:37 +00:00
|
|
|
|
2019-12-11 22:18:40 +00:00
|
|
|
connect(Core(), &CutterCore::functionsChanged, this, &FunctionsWidget::refreshTree);
|
|
|
|
connect(Core(), &CutterCore::codeRebased, this, &FunctionsWidget::refreshTree);
|
|
|
|
connect(Core(), &CutterCore::refreshAll, this, &FunctionsWidget::refreshTree);
|
2017-04-13 15:14:02 +00:00
|
|
|
}
|
|
|
|
|
2017-11-19 12:56:10 +00:00
|
|
|
FunctionsWidget::~FunctionsWidget() {}
|
2017-04-13 15:14:02 +00:00
|
|
|
|
2017-04-28 13:09:40 +00:00
|
|
|
void FunctionsWidget::refreshTree()
|
2017-04-09 19:55:06 +00:00
|
|
|
{
|
2018-06-25 19:28:34 +00:00
|
|
|
if (task) {
|
|
|
|
task->wait();
|
|
|
|
}
|
|
|
|
|
|
|
|
task = QSharedPointer<FunctionsTask>(new FunctionsTask());
|
2018-09-30 20:00:53 +00:00
|
|
|
connect(task.data(), &FunctionsTask::fetchFinished,
|
|
|
|
this, [this] (const QList<FunctionDescription> &functions) {
|
2018-10-10 11:33:55 +00:00
|
|
|
functionModel->beginResetModel();
|
2017-04-28 13:09:40 +00:00
|
|
|
|
2018-06-25 19:28:34 +00:00
|
|
|
this->functions = functions;
|
2017-03-29 10:18:37 +00:00
|
|
|
|
2018-06-25 19:28:34 +00:00
|
|
|
importAddresses.clear();
|
2018-11-26 22:34:34 +00:00
|
|
|
for (const ImportDescription &import : Core()->getAllImports()) {
|
2018-06-25 19:28:34 +00:00
|
|
|
importAddresses.insert(import.plt);
|
|
|
|
}
|
2017-04-28 13:09:40 +00:00
|
|
|
|
2018-06-25 19:28:34 +00:00
|
|
|
mainAdress = (ut64)Core()->cmdj("iMj").object()["vaddr"].toInt();
|
2018-02-12 08:59:45 +00:00
|
|
|
|
2018-10-10 11:33:55 +00:00
|
|
|
functionModel->updateCurrentIndex();
|
|
|
|
functionModel->endResetModel();
|
2017-04-28 13:09:40 +00:00
|
|
|
|
2018-06-25 19:28:34 +00:00
|
|
|
// resize offset and size columns
|
2019-08-19 13:35:25 +00:00
|
|
|
qhelpers::adjustColumns(ui->treeView, 3, 0);
|
2018-06-25 19:28:34 +00:00
|
|
|
});
|
|
|
|
Core()->getAsyncTaskManager()->start(task);
|
2017-03-29 10:18:37 +00:00
|
|
|
}
|
|
|
|
|
2019-03-18 06:44:14 +00:00
|
|
|
void FunctionsWidget::changeSizePolicy(QSizePolicy::Policy hor, QSizePolicy::Policy ver)
|
|
|
|
{
|
|
|
|
ui->dockWidgetContents->setSizePolicy(hor, ver);
|
|
|
|
}
|
|
|
|
|
2019-08-19 13:35:25 +00:00
|
|
|
void FunctionsWidget::onActionFunctionsRenameTriggered()
|
2017-03-29 10:18:37 +00:00
|
|
|
{
|
2017-04-28 13:09:40 +00:00
|
|
|
// Get selected item in functions tree view
|
2019-08-19 13:35:25 +00:00
|
|
|
FunctionDescription function = ui->treeView->selectionModel()->currentIndex().data(
|
2018-03-21 20:32:32 +00:00
|
|
|
FunctionModel::FunctionDescriptionRole).value<FunctionDescription>();
|
2017-04-28 13:09:40 +00:00
|
|
|
|
2020-06-14 08:52:48 +00:00
|
|
|
bool ok;
|
2017-03-29 10:18:37 +00:00
|
|
|
// Create dialog
|
2020-06-14 08:52:48 +00:00
|
|
|
QString newName = QInputDialog::getText(this, tr("Rename function %1").arg(function.name),
|
|
|
|
tr("Function name:"), QLineEdit::Normal, function.name, &ok);
|
2017-03-29 10:18:37 +00:00
|
|
|
// If user accepted
|
2020-06-14 08:52:48 +00:00
|
|
|
if (ok && !newName.isEmpty()) {
|
2017-03-29 10:18:37 +00:00
|
|
|
// Rename function in r2 core
|
2020-07-16 08:56:38 +00:00
|
|
|
Core()->renameFunction(function.offset, newName);
|
2017-04-28 13:09:40 +00:00
|
|
|
|
2017-03-29 10:18:37 +00:00
|
|
|
// Seek to new renamed function
|
2019-07-19 19:21:12 +00:00
|
|
|
Core()->seekAndShow(function.offset);
|
2017-03-29 10:18:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-14 21:54:57 +00:00
|
|
|
|
2019-08-19 13:35:25 +00:00
|
|
|
void FunctionsWidget::onActionFunctionsUndefineTriggered()
|
2017-03-29 10:18:37 +00:00
|
|
|
{
|
2019-08-19 13:35:25 +00:00
|
|
|
const auto selection = ui->treeView->selectionModel()->selection().indexes();
|
|
|
|
std::vector<RVA> offsets;
|
|
|
|
offsets.reserve(selection.size());
|
|
|
|
for (const auto &index : selection) {
|
|
|
|
offsets.push_back(functionProxyModel->address(index));
|
|
|
|
}
|
|
|
|
for (RVA offset : offsets) {
|
|
|
|
Core()->delFunction(offset);
|
|
|
|
}
|
2017-03-29 10:18:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void FunctionsWidget::showTitleContextMenu(const QPoint &pt)
|
|
|
|
{
|
2019-08-19 13:35:25 +00:00
|
|
|
titleContextMenu->exec(this->mapToGlobal(pt));
|
2017-03-29 10:18:37 +00:00
|
|
|
}
|
|
|
|
|
2019-08-19 13:35:25 +00:00
|
|
|
void FunctionsWidget::onActionHorizontalToggled(bool enable)
|
2017-03-29 10:18:37 +00:00
|
|
|
{
|
2019-08-19 13:35:25 +00:00
|
|
|
if (enable) {
|
|
|
|
functionModel->setNested(false);
|
|
|
|
ui->treeView->setIndentation(8);
|
|
|
|
}
|
2017-03-29 10:18:37 +00:00
|
|
|
}
|
|
|
|
|
2019-08-19 13:35:25 +00:00
|
|
|
void FunctionsWidget::onActionVerticalToggled(bool enable)
|
2017-03-29 10:18:37 +00:00
|
|
|
{
|
2019-08-19 13:35:25 +00:00
|
|
|
if (enable) {
|
|
|
|
functionModel->setNested(true);
|
|
|
|
ui->treeView->setIndentation(20);
|
|
|
|
}
|
2017-03-29 10:18:37 +00:00
|
|
|
}
|
|
|
|
|
2019-04-27 17:58:44 +00:00
|
|
|
/**
|
|
|
|
* @brief a SLOT to set the stylesheet for a tooltip
|
2019-08-19 13:35:25 +00:00
|
|
|
*/
|
2019-04-27 17:58:44 +00:00
|
|
|
void FunctionsWidget::setTooltipStylesheet()
|
|
|
|
{
|
|
|
|
setStyleSheet(QString("QToolTip { border-width: 1px; max-width: %1px;" \
|
|
|
|
"opacity: 230; background-color: %2;" \
|
|
|
|
"color: %3; border-color: %3;}")
|
|
|
|
.arg(kMaxTooltipWidth)
|
|
|
|
.arg(Config()->getColor("gui.tooltip.background").name())
|
|
|
|
.arg(Config()->getColor("gui.tooltip.foreground").name()));
|
|
|
|
}
|