Support for previewing in the Graph widget (#2797)

Common functionality has been moved to the DisassemblyPreview namespace.
This commit is contained in:
Petros S 2021-12-05 10:53:45 +02:00 committed by GitHub
parent 6438cc4d50
commit d85383fcfc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 211 additions and 26 deletions

View File

@ -8,6 +8,7 @@ set(SOURCES
dialogs/WriteCommandsDialogs.cpp
widgets/DisassemblerGraphView.cpp
widgets/OverviewView.cpp
common/DisassemblyPreview.cpp
common/RichTextPainter.cpp
dialogs/InitialOptionsDialog.cpp
dialogs/AboutDialog.cpp

View File

@ -167,6 +167,17 @@ public:
void setGraphMinFontSize(int sz) { s.setValue("graph.minfontsize", sz); }
/**
* @brief Get the boolean setting for preview in Graph
* @return True if preview checkbox is checked, false otherwise
*/
bool getGraphPreview() { return s.value("graph.preview").toBool(); }
/**
* @brief Set the boolean setting for preview in Graph
* @param checked is a boolean that represents the preview checkbox
*/
void setGraphPreview(bool checked) { s.setValue("graph.preview", checked); }
/**
* @brief Getters and setters for the transaparent option state and scale factor for bitmap
* graph exports.

View File

@ -0,0 +1,84 @@
#include "DisassemblyPreview.h"
#include "Configuration.h"
#include "widgets/GraphView.h"
#include <QCoreApplication>
#include <QWidget>
#include <QToolTip>
#include <QProcessEnvironment>
DisassemblyTextBlockUserData::DisassemblyTextBlockUserData(const DisassemblyLine &line)
: line { line }
{
}
DisassemblyTextBlockUserData *getUserData(const QTextBlock &block)
{
QTextBlockUserData *userData = block.userData();
if (!userData) {
return nullptr;
}
return static_cast<DisassemblyTextBlockUserData *>(userData);
}
QString DisassemblyPreview::getToolTipStyleSheet()
{
return QString { "QToolTip { border-width: 1px; max-width: %1px;"
"opacity: 230; background-color: %2;"
"color: %3; border-color: %3;}" }
.arg(400)
.arg(Config()->getColor("gui.tooltip.background").name())
.arg(Config()->getColor("gui.tooltip.foreground").name());
}
bool DisassemblyPreview::showDisasPreview(QWidget *parent, const QPoint &pointOfEvent,
const RVA offsetFrom)
{
QProcessEnvironment env;
QPoint point = pointOfEvent;
QList<XrefDescription> refs = Core()->getXRefs(offsetFrom, false, false);
if (refs.length()) {
if (refs.length() > 1) {
qWarning() << QObject::tr(
"More than one (%1) references here. Weird behaviour expected.")
.arg(refs.length());
}
RVA offsetTo = refs.at(0).to; // This is the offset we want to preview
if (Q_UNLIKELY(offsetFrom != refs.at(0).from)) {
qWarning() << QObject::tr("offsetFrom (%1) differs from refs.at(0).from (%(2))")
.arg(offsetFrom)
.arg(refs.at(0).from);
}
/*
* Only if the offset we point *to* is different from the one the cursor is currently
* on *and* the former is a valid offset, we are allowed to get a preview of offsetTo
*/
if (offsetTo != offsetFrom && offsetTo != RVA_INVALID) {
QStringList disasmPreview = Core()->getDisassemblyPreview(offsetTo, 10);
// Last check to make sure the returned preview isn't an empty text (QStringList)
if (!disasmPreview.isEmpty()) {
const QFont &fnt = Config()->getFont();
QFontMetrics fm { fnt };
QString tooltip =
QString { "<html><div style=\"font-family: %1; font-size: %2pt; "
"white-space: nowrap;\"><div style=\"margin-bottom: "
"10px;\"><strong>Disassembly Preview</strong>:<br>%3<div>" }
.arg(fnt.family())
.arg(qMax(8, fnt.pointSize() - 1))
.arg(disasmPreview.join("<br>"));
QToolTip::showText(point, tooltip, parent, QRect {}, 3500);
return true;
}
}
}
return false;
}

View File

@ -0,0 +1,38 @@
#ifndef DISASSEMBLYPREVIEW_H
#define DISASSEMBLYPREVIEW_H
#include <QTextBlockUserData>
#include "core/CutterDescriptions.h"
class QWidget;
class DisassemblyTextBlockUserData : public QTextBlockUserData
{
public:
DisassemblyLine line;
explicit DisassemblyTextBlockUserData(const DisassemblyLine &line);
};
DisassemblyTextBlockUserData *getUserData(const QTextBlock &block);
/**
* @brief Namespace to define relevant functions
*
* @ingroup DisassemblyPreview
*/
namespace DisassemblyPreview {
/*!
* @brief Get the QString that defines the stylesheet for tooltip
* @return A QString for the stylesheet
*/
QString getToolTipStyleSheet();
/*!
* @brief Show a QToolTip that previews the disassembly that is pointed to
* @return True if the tooltip is shown
*/
bool showDisasPreview(QWidget *parent, const QPoint &pointOfEvent, const RVA offsetFrom);
}
#endif

View File

@ -15,6 +15,7 @@ GraphOptionsWidget::GraphOptionsWidget(PreferencesDialog *dialog)
ui->setupUi(this);
ui->checkTransparent->setChecked(Config()->getBitmapTransparentState());
ui->blockEntryCheckBox->setChecked(Config()->getGraphBlockEntryOffset());
ui->graphPreviewCheckBox->setChecked(Config()->getGraphPreview());
ui->bitmapGraphScale->setValue(Config()->getBitmapExportScaleFactor() * 100.0);
updateOptionsFromVars();
@ -84,6 +85,12 @@ void GraphOptionsWidget::on_graphOffsetCheckBox_toggled(bool checked)
triggerOptionsChanged();
}
void GraphOptionsWidget::on_graphPreviewCheckBox_toggled( bool checked )
{
Config()->setGraphPreview(checked);
triggerOptionsChanged();
}
void GraphOptionsWidget::checkTransparentStateChanged(int checked)
{
Config()->setBitmapTransparentState(checked);

View File

@ -33,6 +33,7 @@ private slots:
void on_maxColsSpinBox_valueChanged(int value);
void on_minFontSizeSpinBox_valueChanged(int value);
void on_graphOffsetCheckBox_toggled(bool checked);
void on_graphPreviewCheckBox_toggled(bool checked);
void checkTransparentStateChanged(int checked);
void bitmapGraphScaleValueChanged(double value);

View File

@ -42,6 +42,13 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="graphPreviewCheckBox">
<property name="text">
<string>Show preview when hovering (graph.preview)</string>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">

View File

@ -5,6 +5,7 @@
#include "core/MainWindow.h"
#include "common/Colors.h"
#include "common/Configuration.h"
#include "common/DisassemblyPreview.h"
#include "common/TempConfig.h"
#include "common/SyntaxHighlighter.h"
#include "common/BasicBlockHighlighter.h"
@ -41,7 +42,12 @@ DisassemblerGraphView::DisassemblerGraphView(QWidget *parent, CutterSeekable *se
{
highlight_token = nullptr;
auto *layout = new QVBoxLayout(this);
setTooltipStylesheet();
// Signals that require a refresh all
connect(Config(), &Configuration::colorsUpdated, this,
&DisassemblerGraphView::setTooltipStylesheet);
connect(Core(), &CutterCore::refreshAll, this, &DisassemblerGraphView::refreshView);
connect(Core(), &CutterCore::commentsChanged, this, &DisassemblerGraphView::refreshView);
connect(Core(), &CutterCore::functionRenamed, this, &DisassemblerGraphView::refreshView);
@ -140,6 +146,7 @@ DisassemblerGraphView::DisassemblerGraphView(QWidget *parent, CutterSeekable *se
layout->setAlignment(Qt::AlignTop);
this->scale_thickness_multiplier = true;
installEventFilter(this);
}
void DisassemblerGraphView::connectSeekChanged(bool disconn)
@ -524,6 +531,40 @@ GraphView::EdgeConfiguration DisassemblerGraphView::edgeConfiguration(GraphView:
return ec;
}
bool DisassemblerGraphView::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::Type::ToolTip && Config()->getGraphPreview()) {
QHelpEvent *helpEvent = static_cast<QHelpEvent *>(event);
QPoint pointOfEvent = helpEvent->globalPos();
QPoint point = viewToLogicalCoordinates(helpEvent->pos());
GraphBlock *block = getBlockContaining(point);
if (block == nullptr) {
return false;
}
// offsetFrom is the address which on top the cursor triggered this
RVA offsetFrom = RVA_INVALID;
/*
* getAddrForMouseEvent() doesn't work for jmps, like
* getInstrForMouseEvent() with false as a 3rd argument.
*/
Instr *inst = getInstrForMouseEvent(*block, &point, true);
if (inst != nullptr) {
offsetFrom = inst->addr;
}
// Don't preview anything for a small scale
if (getViewScale() >= 0.8) {
return DisassemblyPreview::showDisasPreview(this, pointOfEvent, offsetFrom);
}
}
return CutterGraphView::eventFilter(obj, event);
}
RVA DisassemblerGraphView::getAddrForMouseEvent(GraphBlock &block, QPoint *point)
{
DisassemblyBlock &db = disassembly_blocks[block.entry];
@ -702,6 +743,11 @@ void DisassemblerGraphView::takeFalse()
}
}
void DisassemblerGraphView::setTooltipStylesheet()
{
setStyleSheet(DisassemblyPreview::getToolTipStyleSheet());
}
void DisassemblerGraphView::seekInstruction(bool previous_instr)
{
RVA addr = seekable->getOffset();
@ -951,3 +997,13 @@ bool DisassemblerGraphView::Instr::contains(ut64 addr) const
{
return this->addr <= addr && (addr - this->addr) < size;
}
RVA DisassemblerGraphView::readDisassemblyOffset(QTextCursor tc)
{
auto userData = getUserData(tc.block());
if (!userData) {
return RVA_INVALID;
}
return userData->line.offset;
}

View File

@ -139,10 +139,13 @@ protected:
QPoint pos) override;
void contextMenuEvent(QContextMenuEvent *event) override;
void restoreCurrentBlock() override;
bool eventFilter(QObject *obj, QEvent *event) override;
private slots:
void showExportDialog() override;
void onActionHighlightBITriggered();
void onActionUnhighlightBITriggered();
void setTooltipStylesheet();
private:
bool transition_dont_seek = false;
@ -175,6 +178,7 @@ private:
DisassemblyBlock *blockForAddress(RVA addr);
void seekLocal(RVA addr, bool update_viewport = true);
void seekInstruction(bool previous_instr);
RVA readDisassemblyOffset(QTextCursor tc);
CutterSeekable *seekable = nullptr;
QList<QShortcut *> shortcuts;

View File

@ -1,6 +1,7 @@
#include "DisassemblyWidget.h"
#include "menus/DisassemblyContextMenu.h"
#include "common/Configuration.h"
#include "common/DisassemblyPreview.h"
#include "common/Helpers.h"
#include "common/TempConfig.h"
#include "common/SelectionHighlight.h"
@ -22,26 +23,6 @@
#include <algorithm>
#include <cmath>
static const int kMaxTooltipWidth = 400;
class DisassemblyTextBlockUserData : public QTextBlockUserData
{
public:
DisassemblyLine line;
explicit DisassemblyTextBlockUserData(const DisassemblyLine &line) { this->line = line; }
};
static DisassemblyTextBlockUserData *getUserData(const QTextBlock &block)
{
QTextBlockUserData *userData = block.userData();
if (!userData) {
return nullptr;
}
return static_cast<DisassemblyTextBlockUserData *>(userData);
}
DisassemblyWidget::DisassemblyWidget(MainWindow *main)
: MemoryDockWidget(MemoryWidgetType::Disassembly, main),
mCtxMenu(new DisassemblyContextMenu(this, main)),
@ -769,12 +750,7 @@ void DisassemblyWidget::setupColors()
.arg(ConfigColor("btext").name()));
// Read and set a stylesheet for the QToolTip too
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()));
setStyleSheet(DisassemblyPreview::getToolTipStyleSheet());
}
DisassemblyScrollArea::DisassemblyScrollArea(QWidget *parent) : QAbstractScrollArea(parent) {}