diff --git a/src/widgets/FunctionsWidget.cpp b/src/widgets/FunctionsWidget.cpp index 50fbcab1..8c8fe7b1 100644 --- a/src/widgets/FunctionsWidget.cpp +++ b/src/widgets/FunctionsWidget.cpp @@ -7,6 +7,7 @@ #include "dialogs/RenameDialog.h" #include "dialogs/XrefsDialog.h" #include "utils/FunctionsTask.h" +#include "utils/TempConfig.h" #include #include @@ -14,8 +15,17 @@ #include #include #include +#include #include +namespace { + +static const int kMaxTooltipWidth = 400; +static const int kMaxTooltipDisasmPreviewLines = 10; +static const int kMaxTooltipHighlightsLines = 5; + +} + FunctionModel::FunctionModel(QList *functions, QSet *importAddresses, ut64 *mainAdress, bool nested, QFont default_font, QFont highlight_font, QObject *parent) : QAbstractItemModel(parent), @@ -186,16 +196,79 @@ QVariant FunctionModel::data(const QModelIndex &index, int role) const return static_cast(Qt::AlignLeft | Qt::AlignVCenter); case Qt::ToolTipRole: { - QList info = Core()->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" + Core()->cmd("pdi 10 @ " + function.name) + - "\nStrings:\n\n" + Core()->cmd("pdsf @ " + function.name)); + const QJsonArray &info = Core()->cmdj("afij @ " + function.name).array(); + if (!info.isEmpty()) { + const QJsonObject &funcInfo = info[0].toObject(); + + static const QStringList kRequiredFields{ "size", "cc", "nbbs" }; + for (const auto& key : kRequiredFields) { + if (!funcInfo.contains(key)) { + return QVariant(); + } + } + const int size = funcInfo["size"].toInt(); + const int complex = funcInfo["cc"].toInt(); + const int bb = funcInfo["nbbs"].toInt(); + QStringList disasmPreview; + + QJsonArray jDisasmEntries; + { + // temporarily simplify the disasm output to get it colorful and simple to read + TempConfig tempConfig; + tempConfig.set("scr.html", true) + .set("scr.color", COLOR_MODE_16M) + .set("asm.lines", false) + .set("asm.var", false) + .set("asm.comments", false) + .set("asm.bytes", false) + .set("asm.lines.fcn", false) + .set("asm.lines.out", false) + .set("asm.lines.bb", false) + .set("asm.bbline", false); + + jDisasmEntries = Core()->cmdj(QString("pDJ %1 @ %2").arg(function.size).arg(function.offset)).array(); + } + for (const QJsonValue &value : jDisasmEntries) { + const QJsonObject &object = value.toObject(); + + disasmPreview << object["text"].toString(); + if (disasmPreview.length() >= kMaxTooltipDisasmPreviewLines) { + disasmPreview << "..."; + break; + } + } + + const QStringList &summary = Core()->cmd("pdsf @ " + function.name).split("\n", QString::SkipEmptyParts); + + const QFont &fnt = Config()->getFont(); + QFontMetrics fm{ fnt }; + + // elide long strings using current didsam font metrics + QStringList highlights; + for (const QString &s : summary) { + highlights << fm.elidedText(s, Qt::ElideRight, kMaxTooltipWidth); + if (highlights.length() > kMaxTooltipHighlightsLines) { + highlights << "..."; + break; + } + } + QString toolTipContent = QString("
") + .arg(fnt.family()) + .arg(qMax(6, fnt.pointSize() - 1)) // slightly decrease font size, to keep more text in the same + + tr("
Summary:
") + + tr("Size: %1, Cyclomatic complexity: %2, Basic blocks: %3") + .arg(size) + .arg(complex) + .arg(bb) + + tr("
Disassembly preview:
%1
") + .arg(disasmPreview.join("
")); + + if (!highlights.isEmpty()) { + toolTipContent += tr("
Highlights:
%1
") + .arg(highlights.join("\n").toHtmlEscaped().replace("\n", "
")); + } + toolTipContent += "
"; + return toolTipContent; } return QVariant(); } @@ -422,6 +495,8 @@ FunctionsWidget::FunctionsWidget(MainWindow *main, QAction *action) : // Radare core found in: this->main = main; + setStyleSheet(QString("QToolTip { max-width: %2px; opacity: 230; }").arg(kMaxTooltipWidth)); + // leave the filter visible by default so users know it exists //ui->filterLineEdit->setVisible(false);