diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 651af4bb..8aae0d5d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 diff --git a/src/common/Configuration.h b/src/common/Configuration.h index 73ed1045..3343e271 100644 --- a/src/common/Configuration.h +++ b/src/common/Configuration.h @@ -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. diff --git a/src/common/DisassemblyPreview.cpp b/src/common/DisassemblyPreview.cpp new file mode 100644 index 00000000..df6fb58b --- /dev/null +++ b/src/common/DisassemblyPreview.cpp @@ -0,0 +1,84 @@ +#include "DisassemblyPreview.h" +#include "Configuration.h" +#include "widgets/GraphView.h" + +#include +#include +#include +#include + +DisassemblyTextBlockUserData::DisassemblyTextBlockUserData(const DisassemblyLine &line) + : line { line } +{ +} + +DisassemblyTextBlockUserData *getUserData(const QTextBlock &block) +{ + QTextBlockUserData *userData = block.userData(); + if (!userData) { + return nullptr; + } + + return static_cast(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 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 { "
Disassembly Preview:
%3
" } + .arg(fnt.family()) + .arg(qMax(8, fnt.pointSize() - 1)) + .arg(disasmPreview.join("
")); + + QToolTip::showText(point, tooltip, parent, QRect {}, 3500); + return true; + } + } + } + return false; +} diff --git a/src/common/DisassemblyPreview.h b/src/common/DisassemblyPreview.h new file mode 100644 index 00000000..656b818e --- /dev/null +++ b/src/common/DisassemblyPreview.h @@ -0,0 +1,38 @@ +#ifndef DISASSEMBLYPREVIEW_H +#define DISASSEMBLYPREVIEW_H + +#include + +#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 diff --git a/src/dialogs/preferences/GraphOptionsWidget.cpp b/src/dialogs/preferences/GraphOptionsWidget.cpp index e3980d09..7a896174 100644 --- a/src/dialogs/preferences/GraphOptionsWidget.cpp +++ b/src/dialogs/preferences/GraphOptionsWidget.cpp @@ -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); diff --git a/src/dialogs/preferences/GraphOptionsWidget.h b/src/dialogs/preferences/GraphOptionsWidget.h index 7b69dd19..e69c9bb1 100644 --- a/src/dialogs/preferences/GraphOptionsWidget.h +++ b/src/dialogs/preferences/GraphOptionsWidget.h @@ -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); diff --git a/src/dialogs/preferences/GraphOptionsWidget.ui b/src/dialogs/preferences/GraphOptionsWidget.ui index d4fbca05..7d9c8c5e 100644 --- a/src/dialogs/preferences/GraphOptionsWidget.ui +++ b/src/dialogs/preferences/GraphOptionsWidget.ui @@ -42,6 +42,13 @@ + + + + Show preview when hovering (graph.preview) + + + diff --git a/src/widgets/DisassemblerGraphView.cpp b/src/widgets/DisassemblerGraphView.cpp index 9df66ac4..7f434ff6 100644 --- a/src/widgets/DisassemblerGraphView.cpp +++ b/src/widgets/DisassemblerGraphView.cpp @@ -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(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; +} diff --git a/src/widgets/DisassemblerGraphView.h b/src/widgets/DisassemblerGraphView.h index beb2b209..07bfc9ed 100644 --- a/src/widgets/DisassemblerGraphView.h +++ b/src/widgets/DisassemblerGraphView.h @@ -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 shortcuts; diff --git a/src/widgets/DisassemblyWidget.cpp b/src/widgets/DisassemblyWidget.cpp index 3e933ccd..97c83e64 100644 --- a/src/widgets/DisassemblyWidget.cpp +++ b/src/widgets/DisassemblyWidget.cpp @@ -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 #include -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(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) {}