mirror of
https://github.com/rizinorg/cutter.git
synced 2025-01-30 16:25:04 +00:00
refactor DisassemblyLeftPanel::paintEvent (#2559)
This commit is contained in:
parent
8f89d1641b
commit
00d2245538
@ -396,4 +396,112 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Structure for keeping track of minimum and maximum value set at each position.
|
||||||
|
*
|
||||||
|
* Supports range update and range query.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* @code{.cpp}
|
||||||
|
* MinMaxAccumulateTree t(30); // operate within range [0; 30)
|
||||||
|
* t.updateRange(1, 5, 10);
|
||||||
|
* rangeMinMax(0, 20);// -> {10, 10}
|
||||||
|
* t.updateRange(4, 6, 15);
|
||||||
|
* t.updateRange(3, 10, 20);
|
||||||
|
* t.rangeMinMax(0, 20); // -> {10, 20}
|
||||||
|
* t.rangeMinMax(1, 3); // -> {10, 10}
|
||||||
|
* t.rangeMinMax(5, 8); // -> {15, 20}
|
||||||
|
* @endcode
|
||||||
|
*/
|
||||||
|
template<class IntegerType>
|
||||||
|
class MinMaxAccumulateTree : public LazySegmentTreeBase<std::pair<IntegerType, IntegerType>, std::pair<IntegerType, IntegerType>, MinMaxAccumulateTree<IntegerType>>
|
||||||
|
{
|
||||||
|
// Could work with other types but that would require changing LIMITS
|
||||||
|
static_assert (std::is_integral<IntegerType>::value, "Template argument IntegerType must be integer");
|
||||||
|
using MinMax = std::pair<IntegerType, IntegerType>;
|
||||||
|
using ValueType = MinMax;
|
||||||
|
using ThisType = MinMaxAccumulateTree<IntegerType>;
|
||||||
|
using BaseType = LazySegmentTreeBase<ValueType, MinMax, ThisType>;
|
||||||
|
using NodeType = typename BaseType::NodeType;
|
||||||
|
using NodePosition = typename BaseType::NodePosition;
|
||||||
|
|
||||||
|
static constexpr MinMax LIMITS()
|
||||||
|
{
|
||||||
|
return {std::numeric_limits<IntegerType>::max(),
|
||||||
|
std::numeric_limits<IntegerType>::min()};
|
||||||
|
}
|
||||||
|
|
||||||
|
static MinMax Combine(const MinMax &a, const MinMax &b)
|
||||||
|
{
|
||||||
|
return {std::min(a.first, b.first), std::max(a.second, b.second)};
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateNode(NodePosition nodePos, ValueType value)
|
||||||
|
{
|
||||||
|
this->nodes[nodePos] = Combine(this->nodes[nodePos], value);
|
||||||
|
if (!this->isLeave(nodePos)) {
|
||||||
|
this->promise[nodePos] = Combine(this->promise[nodePos], value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
MinMaxAccumulateTree(size_t size, ValueType initialValue = LIMITS())
|
||||||
|
: BaseType(size, initialValue, LIMITS())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateFromChildren(NodeType &parent, const NodeType &left, const NodeType &right)
|
||||||
|
{
|
||||||
|
parent = Combine(left, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pushDown(NodePosition parent)
|
||||||
|
{
|
||||||
|
size_t left = (parent << 1);
|
||||||
|
size_t right = (parent << 1) | 1;
|
||||||
|
this->UpdateNode(left, this->promise[parent]);
|
||||||
|
this->UpdateNode(right, this->promise[parent]);
|
||||||
|
this->promise[parent] = this->neutralPromiseElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Update min and max values in the range [\a left, \a right) with number \a value.
|
||||||
|
* @param left inclusive range left side
|
||||||
|
* @param right exclusive right side of range
|
||||||
|
* @param value number to be used for updating minimum and maximum
|
||||||
|
*/
|
||||||
|
void updateRange(size_t left, size_t right, IntegerType value)
|
||||||
|
{
|
||||||
|
left = this->leaveIndexToPosition(left);
|
||||||
|
right = this->leaveIndexToPosition(right);
|
||||||
|
this->pushDownFromRoot(left);
|
||||||
|
this->pushDownFromRoot(right - 1);
|
||||||
|
MinMax pairValue{value, value};
|
||||||
|
for (size_t l = left, r = right; l < r; l >>= 1, r >>= 1) {
|
||||||
|
if (l & 1) {
|
||||||
|
UpdateNode(l, pairValue);
|
||||||
|
l += 1;
|
||||||
|
}
|
||||||
|
if (r & 1) {
|
||||||
|
r -= 1;
|
||||||
|
UpdateNode(r, pairValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this->updateUntilRoot(left);
|
||||||
|
this->updateUntilRoot(right - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculate minimum and maximum value in the range [l, r)
|
||||||
|
* @param l inclusive left side of range
|
||||||
|
* @param r exclusive right side of range
|
||||||
|
* @return std::pair {min, max}
|
||||||
|
*/
|
||||||
|
MinMax rangeMinMax(size_t l, size_t r)
|
||||||
|
{
|
||||||
|
return this->rangeOperation(l, r, this->neutralPromiseElement);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
#endif // BINARY_TREES_H
|
#endif // BINARY_TREES_H
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
#include "common/Helpers.h"
|
#include "common/Helpers.h"
|
||||||
#include "common/TempConfig.h"
|
#include "common/TempConfig.h"
|
||||||
#include "common/SelectionHighlight.h"
|
#include "common/SelectionHighlight.h"
|
||||||
|
#include "common/BinaryTrees.h"
|
||||||
#include "core/MainWindow.h"
|
#include "core/MainWindow.h"
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
@ -18,7 +19,10 @@
|
|||||||
#include <QPainterPath>
|
#include <QPainterPath>
|
||||||
#include <QSplitter>
|
#include <QSplitter>
|
||||||
|
|
||||||
class DisassemblyTextBlockUserData : public QTextBlockUserData
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
class DisassemblyTextBlockUserData: public QTextBlockUserData
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
DisassemblyLine line;
|
DisassemblyLine line;
|
||||||
@ -833,24 +837,10 @@ void DisassemblyWidget::seekPrev()
|
|||||||
* Left panel
|
* Left panel
|
||||||
*********************/
|
*********************/
|
||||||
|
|
||||||
struct Range
|
|
||||||
{
|
|
||||||
Range(RVA v1, RVA v2) : from(v1), to(v2)
|
|
||||||
{
|
|
||||||
if (from > to)
|
|
||||||
std::swap(from, to);
|
|
||||||
}
|
|
||||||
RVA from;
|
|
||||||
RVA to;
|
|
||||||
|
|
||||||
inline bool contains(const Range &other) const { return from <= other.from && to >= other.to; }
|
|
||||||
|
|
||||||
inline bool contains(RVA point) const { return from <= point && to >= point; }
|
|
||||||
};
|
|
||||||
|
|
||||||
DisassemblyLeftPanel::DisassemblyLeftPanel(DisassemblyWidget *disas)
|
DisassemblyLeftPanel::DisassemblyLeftPanel(DisassemblyWidget *disas)
|
||||||
{
|
{
|
||||||
this->disas = disas;
|
this->disas = disas;
|
||||||
|
arrows.reserve((arrowsSize * 3) / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisassemblyLeftPanel::wheelEvent(QWheelEvent *event)
|
void DisassemblyLeftPanel::wheelEvent(QWheelEvent *event)
|
||||||
@ -865,7 +855,6 @@ void DisassemblyLeftPanel::paintEvent(QPaintEvent *event)
|
|||||||
{
|
{
|
||||||
Q_UNUSED(event)
|
Q_UNUSED(event)
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
constexpr int penSizePix = 1;
|
constexpr int penSizePix = 1;
|
||||||
constexpr int distanceBetweenLines = 10;
|
constexpr int distanceBetweenLines = 10;
|
||||||
constexpr int arrowWidth = 5;
|
constexpr int arrowWidth = 5;
|
||||||
@ -883,99 +872,114 @@ void DisassemblyLeftPanel::paintEvent(QPaintEvent *event)
|
|||||||
|
|
||||||
QList<DisassemblyLine> lines = disas->getLines();
|
QList<DisassemblyLine> lines = disas->getLines();
|
||||||
|
|
||||||
QMap<RVA, int> linesPixPosition;
|
using LineInfo = std::pair<RVA, int>;
|
||||||
QMap<RVA, pair<RVA, int>> arrowInfo; /* offset -> (arrow, layer of arrow) */
|
std::vector<LineInfo> lineOffsets;
|
||||||
int nLines = 0;
|
lineOffsets.reserve(lines.size());
|
||||||
for (const auto &line : lines) {
|
|
||||||
linesPixPosition[line.offset] = nLines * lineHeight + lineHeight / 2 + topOffset;
|
|
||||||
nLines++;
|
|
||||||
if (line.arrow != RVA_INVALID) {
|
|
||||||
arrowInfo.insert(line.offset, { line.arrow, -1 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto it = arrowInfo.begin(); it != arrowInfo.end(); it++) {
|
for (int i = 0; i < lines.size(); i++) {
|
||||||
Range currRange = { it.key(), it.value().first };
|
lineOffsets.emplace_back(lines[i].offset, i);
|
||||||
it.value().second = it.value().second == -1 ? 1 : it.value().second;
|
if (lines[i].arrow != RVA_INVALID) {
|
||||||
for (auto innerIt = arrowInfo.begin(); innerIt != arrowInfo.end(); innerIt++) {
|
Arrow a { lines[i].offset, lines[i].arrow };
|
||||||
if (innerIt == it) {
|
bool contains = std::find_if(std::begin(arrows), std::end(arrows), [&](const Arrow& it) {
|
||||||
continue;
|
return it.min == a.min && it.max == a.max;
|
||||||
}
|
}) != std::end(arrows);
|
||||||
Range innerRange = { innerIt.key(), innerIt.value().first };
|
if (!contains) {
|
||||||
if (currRange.contains(innerRange) || currRange.contains(innerRange.from)) {
|
arrows.emplace_back(lines[i].offset, lines[i].arrow);
|
||||||
it.value().second++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// I'm sorry this loop below, but it is only way I see how to implement the feature
|
auto offsetToLine = [&](RVA offset) -> int {
|
||||||
while (true) {
|
// binary search because linesPixPosition is sorted by offset
|
||||||
bool correction = false;
|
if (lineOffsets.empty()) {
|
||||||
bool correction2 = false;
|
return 0;
|
||||||
for (auto it = arrowInfo.begin(); it != arrowInfo.end(); it++) {
|
|
||||||
int minDistance = INT32_MAX;
|
|
||||||
Range currRange = { it.key(), it.value().first };
|
|
||||||
for (auto innerIt = arrowInfo.begin(); innerIt != arrowInfo.end(); innerIt++) {
|
|
||||||
if (innerIt == it) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
Range innerRange = { innerIt.key(), innerIt.value().first };
|
if (offset < lineOffsets[0].first) {
|
||||||
if (it.value().second == innerIt.value().second
|
return -2;
|
||||||
&& (currRange.contains(innerRange) || currRange.contains(innerRange.from))) {
|
|
||||||
it.value().second++;
|
|
||||||
correction = true;
|
|
||||||
}
|
}
|
||||||
int distance = it.value().second - innerIt.value().second;
|
auto res = lower_bound(std::begin(lineOffsets), std::end(lineOffsets), offset, [](const LineInfo& it, RVA offset) {
|
||||||
if (distance > 0 && distance < minDistance) {
|
return it.first < offset;
|
||||||
minDistance = distance;
|
});
|
||||||
|
if (res == std::end(lineOffsets)) {
|
||||||
|
return lines.size() + 2;
|
||||||
}
|
}
|
||||||
|
return res->second;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
RVA visibleTop = lineOffsets[0].first, visibleBottom = lineOffsets.back().first;
|
||||||
|
auto fitsInScreen = [&](const Arrow &a) {
|
||||||
|
return visibleBottom - visibleTop < a.length();
|
||||||
|
};
|
||||||
|
|
||||||
|
std::sort(std::begin(arrows), std::end(arrows), [&](const Arrow& l, const Arrow& r) {
|
||||||
|
int lScreen = fitsInScreen(l), rScreen = fitsInScreen(r);
|
||||||
|
if (lScreen != rScreen) {
|
||||||
|
return lScreen < rScreen;
|
||||||
}
|
}
|
||||||
if (minDistance > 1 && minDistance != INT32_MAX) {
|
return l.max != r.max ? l.max < r.max : l.min > r.min;
|
||||||
correction2 = true;
|
});
|
||||||
it.value().second -= minDistance - 1;
|
|
||||||
|
RVA max = 0;
|
||||||
|
RVA min = RVA_MAX;
|
||||||
|
for (auto& it : arrows) {
|
||||||
|
min = std::min(it.min, min);
|
||||||
|
max = std::max(it.max, max);
|
||||||
|
it.level = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t maxLevel = 0;
|
||||||
|
if (!arrows.empty()) {
|
||||||
|
MinMaxAccumulateTree<uint32_t> maxLevelTree(max - min + 2);
|
||||||
|
for (Arrow &arrow : arrows) {
|
||||||
|
RVA top = arrow.min >= min ? arrow.min - min + 1: 0;
|
||||||
|
RVA bottom = std::min(arrow.max - min, max - min) + 2;
|
||||||
|
auto minMax = maxLevelTree.rangeMinMax(top, bottom);
|
||||||
|
if (minMax.first > 1) {
|
||||||
|
arrow.level = 1;
|
||||||
|
} else {
|
||||||
|
arrow.level = minMax.second + 1;
|
||||||
|
maxLevel = std::max(maxLevel, arrow.level);
|
||||||
}
|
}
|
||||||
if (!correction && !correction2) {
|
maxLevelTree.updateRange(top, bottom, arrow.level);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const RVA currOffset = disas->getSeekable()->getOffset();
|
const RVA currOffset = disas->getSeekable()->getOffset();
|
||||||
qreal pixelRatio = qhelpers::devicePixelRatio(p.device());
|
const qreal pixelRatio = qhelpers::devicePixelRatio(p.device());
|
||||||
|
const Arrow visibleRange { lines.first().offset, lines.last().offset };
|
||||||
// Draw the lines
|
// Draw the lines
|
||||||
for (const auto &l : lines) {
|
for (const auto& arrow : arrows) {
|
||||||
int lineOffset =
|
if (!visibleRange.intersects(arrow)) {
|
||||||
int((distanceBetweenLines * arrowInfo[l.offset].second + distanceBetweenLines)
|
|
||||||
* pixelRatio);
|
|
||||||
// Skip until we reach a line that jumps to a destination
|
|
||||||
if (l.arrow == RVA_INVALID) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
int lineOffset = int((distanceBetweenLines * arrow.level + distanceBetweenLines) * pixelRatio);
|
||||||
|
|
||||||
bool jumpDown = l.arrow > l.offset;
|
p.setPen(arrow.up ? penUp : penDown);
|
||||||
p.setPen(jumpDown ? penDown : penUp);
|
if (arrow.min == currOffset || arrow.max == currOffset) {
|
||||||
if (l.offset == currOffset || l.arrow == currOffset) {
|
|
||||||
QPen pen = p.pen();
|
QPen pen = p.pen();
|
||||||
pen.setWidthF((penSizePix * 3) / 2.0);
|
pen.setWidthF((penSizePix * 3) / 2.0);
|
||||||
p.setPen(pen);
|
p.setPen(pen);
|
||||||
}
|
}
|
||||||
bool endVisible = true;
|
|
||||||
|
|
||||||
int currentLineYPos = linesPixPosition[l.offset];
|
auto lineToPixels = [&] (int i) {
|
||||||
int lineArrowY = linesPixPosition.value(l.arrow, -1);
|
int offset = int(arrow.up ? std::floor(pixelRatio) : -std::floor(pixelRatio));
|
||||||
|
return i * lineHeight + lineHeight / 2 + topOffset + offset;
|
||||||
|
};
|
||||||
|
|
||||||
if (lineArrowY == -1) {
|
int lineStartNumber = offsetToLine(arrow.jmpFromOffset());
|
||||||
lineArrowY = jumpDown ? geometry().bottom() : 0;
|
int currentLineYPos = lineToPixels(lineStartNumber);
|
||||||
endVisible = false;
|
|
||||||
}
|
int arrowLineNumber = offsetToLine(arrow.jmpToffset());
|
||||||
|
int lineArrowY = lineToPixels(arrowLineNumber);
|
||||||
|
|
||||||
// Draw the lines
|
// Draw the lines
|
||||||
p.drawLine(rightOffset, currentLineYPos, rightOffset - lineOffset, currentLineYPos);
|
p.drawLine(rightOffset, currentLineYPos, rightOffset - lineOffset, currentLineYPos); // left
|
||||||
p.drawLine(rightOffset - lineOffset, currentLineYPos, rightOffset - lineOffset, lineArrowY);
|
p.drawLine(rightOffset - lineOffset, currentLineYPos, rightOffset - lineOffset, lineArrowY); // horizontal
|
||||||
|
|
||||||
if (endVisible) {
|
p.drawLine(rightOffset - lineOffset, lineArrowY, rightOffset, lineArrowY); // right
|
||||||
p.drawLine(rightOffset - lineOffset, lineArrowY, rightOffset, lineArrowY);
|
|
||||||
|
|
||||||
|
{ // triangle
|
||||||
QPainterPath arrow;
|
QPainterPath arrow;
|
||||||
arrow.moveTo(rightOffset - arrowWidth, lineArrowY + arrowWidth);
|
arrow.moveTo(rightOffset - arrowWidth, lineArrowY + arrowWidth);
|
||||||
arrow.lineTo(rightOffset - arrowWidth, lineArrowY - arrowWidth);
|
arrow.lineTo(rightOffset - arrowWidth, lineArrowY - arrowWidth);
|
||||||
@ -983,4 +987,23 @@ void DisassemblyLeftPanel::paintEvent(QPaintEvent *event)
|
|||||||
p.fillPath(arrow, p.pen().brush());
|
p.fillPath(arrow, p.pen().brush());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (maxLevel > maxLevelBeforeFlush) {
|
||||||
|
arrows.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t eraseN = arrows.size() > arrowsSize ? arrows.size() - arrowsSize: 0;
|
||||||
|
if (eraseN > 0) {
|
||||||
|
const bool scrolledDown = lastBeginOffset > lines.first().offset;
|
||||||
|
std::sort(std::begin(arrows), std::end(arrows), [&](const Arrow& l, const Arrow& r) {
|
||||||
|
if (scrolledDown) {
|
||||||
|
return l.jmpFromOffset() < r.jmpFromOffset();
|
||||||
|
} else {
|
||||||
|
return l.jmpFromOffset() > r.jmpFromOffset();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
arrows.erase(std::end(arrows) - eraseN, std::end(arrows));
|
||||||
|
}
|
||||||
|
|
||||||
|
lastBeginOffset = lines.first().offset;
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,9 @@
|
|||||||
#include <QShortcut>
|
#include <QShortcut>
|
||||||
#include <QAction>
|
#include <QAction>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
|
||||||
class DisassemblyTextEdit;
|
class DisassemblyTextEdit;
|
||||||
class DisassemblyScrollArea;
|
class DisassemblyScrollArea;
|
||||||
class DisassemblyContextMenu;
|
class DisassemblyContextMenu;
|
||||||
@ -155,6 +158,40 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
DisassemblyWidget *disas;
|
DisassemblyWidget *disas;
|
||||||
|
|
||||||
|
struct Arrow {
|
||||||
|
Arrow(RVA v1, RVA v2)
|
||||||
|
: min(v1), max(v2),
|
||||||
|
level(0), up(false)
|
||||||
|
{
|
||||||
|
if (min > max) {
|
||||||
|
std::swap(min, max);
|
||||||
|
up = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool contains(RVA point) const
|
||||||
|
{ return min <= point && max >= point; }
|
||||||
|
|
||||||
|
inline bool intersects(const Arrow& other) const
|
||||||
|
{ return std::max(min, other.min) <= std::min(max, other.max); }
|
||||||
|
|
||||||
|
ut64 length() const { return max - min; }
|
||||||
|
|
||||||
|
RVA jmpFromOffset() const { return up ? max : min; }
|
||||||
|
|
||||||
|
RVA jmpToffset() const { return up ? min : max; }
|
||||||
|
|
||||||
|
RVA min;
|
||||||
|
RVA max;
|
||||||
|
uint32_t level;
|
||||||
|
bool up;
|
||||||
|
};
|
||||||
|
|
||||||
|
const size_t arrowsSize = 128;
|
||||||
|
const uint32_t maxLevelBeforeFlush = 32;
|
||||||
|
RVA lastBeginOffset = 0;
|
||||||
|
std::vector<Arrow> arrows;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // DISASSEMBLYWIDGET_H
|
#endif // DISASSEMBLYWIDGET_H
|
||||||
|
Loading…
Reference in New Issue
Block a user