#include "SimpleTextGraphView.h" #include "core/Cutter.h" #include "core/MainWindow.h" #include "common/Configuration.h" #include "common/SyntaxHighlighter.h" #include "common/Helpers.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include SimpleTextGraphView::SimpleTextGraphView(QWidget *parent, MainWindow *mainWindow) : CutterGraphView(parent), contextMenu(new QMenu(this)), addressableItemContextMenu(this, mainWindow), copyAction(tr("Copy"), this) { copyAction.setShortcut(QKeySequence::StandardKey::Copy); copyAction.setShortcutContext(Qt::WidgetShortcut); connect(©Action, &QAction::triggered, this, &SimpleTextGraphView::copyBlockText); contextMenu->addAction(©Action); contextMenu->addAction(&actionExportGraph); contextMenu->addMenu(layoutMenu); addressableItemContextMenu.insertAction(addressableItemContextMenu.actions().first(), ©Action); addressableItemContextMenu.addSeparator(); addressableItemContextMenu.addAction(&actionExportGraph); addressableItemContextMenu.addMenu(layoutMenu); addActions(addressableItemContextMenu.actions()); addAction(©Action); enableAddresses(haveAddresses); } SimpleTextGraphView::~SimpleTextGraphView() { for (QShortcut *shortcut : shortcuts) { delete shortcut; } } void SimpleTextGraphView::refreshView() { initFont(); setLayoutConfig(getLayoutConfig()); saveCurrentBlock(); loadCurrentGraph(); if (blocks.find(selectedBlock) == blocks.end()) { selectedBlock = NO_BLOCK_SELECTED; } restoreCurrentBlock(); emit viewRefreshed(); } void SimpleTextGraphView::selectBlockWithId(ut64 blockId) { if (!enableBlockSelection) { return; } auto contentIt = blockContent.find(blockId); if (contentIt != blockContent.end()) { selectedBlock = blockId; if (haveAddresses) { addressableItemContextMenu.setTarget(contentIt->second.address, contentIt->second.text); } viewport()->update(); } else { selectedBlock = NO_BLOCK_SELECTED; } } void SimpleTextGraphView::drawBlock(QPainter &p, GraphView::GraphBlock &block, bool interactive) { QRectF blockRect(block.x, block.y, block.width, block.height); const qreal padding = charWidth; p.setPen(Qt::black); p.setBrush(Qt::gray); p.setFont(Config()->getFont()); p.drawRect(blockRect); // Render node auto &content = blockContent[block.entry]; p.setPen(QColor(0, 0, 0, 0)); p.setBrush(QColor(0, 0, 0, 100)); p.setPen(QPen(graphNodeColor, 1)); bool blockSelected = interactive && (block.entry == selectedBlock); if (blockSelected) { p.setBrush(disassemblySelectedBackgroundColor); } else { p.setBrush(disassemblyBackgroundColor); } // Draw basic block background p.drawRect(blockRect); // Stop rendering text when it's too small auto transform = p.combinedTransform(); QRect screenChar = transform.mapRect(QRect(0, 0, charWidth, charHeight)); if (screenChar.width() < Config()->getGraphMinFontSize()) { return; } p.setPen(palette().color(QPalette::WindowText)); // Render node text auto x = block.x + padding; int y = block.y + padding + p.fontMetrics().ascent(); p.drawText(QPoint(x, y), content.text); } GraphView::EdgeConfiguration SimpleTextGraphView::edgeConfiguration(GraphView::GraphBlock &from, GraphView::GraphBlock *to, bool interactive) { EdgeConfiguration ec; ec.color = jmpColor; ec.start_arrow = false; ec.end_arrow = true; if (interactive && (selectedBlock == from.entry || selectedBlock == to->entry)) { ec.width_scale = 2.0; } return ec; } void SimpleTextGraphView::setBlockSelectionEnabled(bool value) { enableBlockSelection = value; if (!value) { selectedBlock = NO_BLOCK_SELECTED; } } void SimpleTextGraphView::addBlock(GraphLayout::GraphBlock block, const QString &text, RVA address) { auto &content = blockContent[block.entry]; content.text = text; content.address = address; int height = 1; int width = mFontMetrics->width(text); int extra = static_cast(2 * charWidth); block.width = static_cast(width + extra); block.height = (height * charHeight) + extra; GraphView::addBlock(std::move(block)); } void SimpleTextGraphView::enableAddresses(bool enabled) { haveAddresses = enabled; if (!enabled) { addressableItemContextMenu.clearTarget(); // Clearing addreassable item context menu disables all the actions inside it including extra ones added // by SimpleTextGraphView. Re-enable them because they are in the regular context menu as well and shouldn't be // disabled. for (auto action : contextMenu->actions()) { action->setEnabled(true); } }; } void SimpleTextGraphView::copyBlockText() { auto blockIt = blockContent.find(selectedBlock); if (blockIt != blockContent.end()) { QClipboard *clipboard = QApplication::clipboard(); clipboard->setText(blockIt->second.text); } } void SimpleTextGraphView::contextMenuEvent(QContextMenuEvent *event) { GraphView::contextMenuEvent(event); if (!event->isAccepted() && event->reason() != QContextMenuEvent::Mouse && enableBlockSelection && selectedBlock != NO_BLOCK_SELECTED) { auto blockIt = blocks.find(selectedBlock); if (blockIt != blocks.end()) { blockContextMenuRequested(blockIt->second, event, {}); } } if (!event->isAccepted()) { contextMenu->exec(event->globalPos()); event->accept(); } } void SimpleTextGraphView::blockContextMenuRequested(GraphView::GraphBlock &block, QContextMenuEvent *event, QPoint /*pos*/) { if (haveAddresses) { const auto &content = blockContent[block.entry]; addressableItemContextMenu.setTarget(content.address, content.text); QPoint pos = event->globalPos(); if (event->reason() != QContextMenuEvent::Mouse) { QPoint blockPosition(block.x + block.width / 2, block.y + block.height / 2); blockPosition = logicalToViewCoordinates(blockPosition); if (viewport()->rect().contains(blockPosition)) { pos = mapToGlobal(blockPosition); } } addressableItemContextMenu.exec(pos); event->accept(); } } void SimpleTextGraphView::blockHelpEvent(GraphView::GraphBlock &block, QHelpEvent *event, QPoint /*pos*/) { if (haveAddresses) { QToolTip::showText(event->globalPos(), RAddressString(blockContent[block.entry].address)); } } void SimpleTextGraphView::blockClicked(GraphView::GraphBlock &block, QMouseEvent *event, QPoint /*pos*/) { if ((event->button() == Qt::LeftButton || event->button() == Qt::RightButton) && enableBlockSelection) { selectBlockWithId(block.entry); } } void SimpleTextGraphView::restoreCurrentBlock() { if (enableBlockSelection) { auto blockIt = blocks.find(selectedBlock); if (blockIt != blocks.end()) { showBlock(blockIt->second, true); } } } void SimpleTextGraphView::paintEvent(QPaintEvent *event) { // SimpleTextGraphView is always dirty setCacheDirty(); GraphView::paintEvent(event); }