Improve arrow widget

This commit is contained in:
Unknown 2019-06-30 01:26:48 +03:00 committed by xarkes
parent 33dca54176
commit 77a7710daf
2 changed files with 144 additions and 46 deletions

View File

@ -2709,7 +2709,10 @@ QList<DisassemblyLine> CutterCore::disassembleLines(RVA offset, int lines)
DisassemblyLine line; DisassemblyLine line;
line.offset = object[RJsonKey::offset].toVariant().toULongLong(); line.offset = object[RJsonKey::offset].toVariant().toULongLong();
line.text = ansiEscapeToHtml(object[RJsonKey::text].toString()); line.text = ansiEscapeToHtml(object[RJsonKey::text].toString());
line.arrow = object[RJsonKey::arrow].toVariant().toULongLong(); const auto& arrow = object[RJsonKey::arrow];
line.arrow = arrow.isNull()
? RVA_INVALID
: arrow.toVariant().toULongLong();
r << line; r << line;
} }

View File

@ -6,6 +6,7 @@
#include "common/SelectionHighlight.h" #include "common/SelectionHighlight.h"
#include "core/MainWindow.h" #include "core/MainWindow.h"
#include <QApplication>
#include <QScrollBar> #include <QScrollBar>
#include <QJsonArray> #include <QJsonArray>
#include <QJsonObject> #include <QJsonObject>
@ -67,14 +68,38 @@ DisassemblyWidget::DisassemblyWidget(MainWindow *main, QAction *action)
layout->setMargin(0); layout->setMargin(0);
mDisasScrollArea->viewport()->setLayout(layout); mDisasScrollArea->viewport()->setLayout(layout);
splitter->addWidget(mDisasScrollArea); splitter->addWidget(mDisasScrollArea);
mDisasScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff);
// Use stylesheet instead of QWidget::setFrameShape(QFrame::NoShape) to avoid
// issues with dark and light interface themes
mDisasScrollArea->setStyleSheet("border: 0px transparent black;");
mDisasTextEdit->setStyleSheet("border: 0px transparent black;");
mDisasTextEdit->setFocusProxy(this);
mDisasTextEdit->setFocusPolicy(Qt::ClickFocus);
mDisasScrollArea->setFocusProxy(this);
mDisasScrollArea->setFocusPolicy(Qt::ClickFocus);
setFocusPolicy(Qt::ClickFocus);
// Behave like all widgets: highlight on focus and hover
connect(qApp, &QApplication::focusChanged, this, [this](QWidget* , QWidget* now) {
QColor borderColor = this == now
? palette().color(QPalette::Highlight)
: palette().color(QPalette::WindowText).darker();
widget()->setStyleSheet(QString("QSplitter { border: %1px solid %2 } \n"
"QSplitter:hover { border: %1px solid %3 } \n")
.arg(devicePixelRatio())
.arg(borderColor.name())
.arg(palette().color(QPalette::Highlight).name()));
});
splitter->setFrameShape(QFrame::Box);
// Set current widget to the splitted layout we just created // Set current widget to the splitted layout we just created
setWidget(splitter); setWidget(splitter);
// Resize properly // Resize properly
QList<int> sizes; QList<int> sizes;
sizes.append(3); sizes.append(3);
sizes.append(1); // TODO Probably not the best way to go sizes.append(1);
splitter->setSizes(sizes); splitter->setSizes(sizes);
setAllowedAreas(Qt::AllDockWidgetAreas); setAllowedAreas(Qt::AllDockWidgetAreas);
@ -554,6 +579,7 @@ void DisassemblyWidget::cursorPositionChanged()
// No word is selected so use the word under the cursor // No word is selected so use the word under the cursor
mCtxMenu->setCurHighlightedWord(curHighlightedWord); mCtxMenu->setCurHighlightedWord(curHighlightedWord);
} }
leftPanel->update();
} }
void DisassemblyWidget::moveCursorRelative(bool up, bool page) void DisassemblyWidget::moveCursorRelative(bool up, bool page)
@ -764,6 +790,24 @@ 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;
@ -771,76 +815,127 @@ DisassemblyLeftPanel::DisassemblyLeftPanel(DisassemblyWidget *disas)
void DisassemblyLeftPanel::paintEvent(QPaintEvent *event) void DisassemblyLeftPanel::paintEvent(QPaintEvent *event)
{ {
Q_UNUSED(event); Q_UNUSED(event)
RVA currentOffset = Core()->getOffset(); // TODO Use the seekable from DisassemblyWidget using namespace std;
constexpr int penSizePix = 2;
constexpr int distanceBetweenLines = 10;
constexpr int arrowWidth = 5;
int rightOffset = size().rwidth(); int rightOffset = size().rwidth();
auto tEdit = qobject_cast<DisassemblyTextEdit*>(disas->getTextWidget());
int topOffset = int(tEdit->document()->documentMargin() + tEdit->contentsMargins().top() +
disas->contentsMargins().top());
int lineHeight = disas->getFontMetrics().height(); int lineHeight = disas->getFontMetrics().height();
QColor arrowColor = ConfigColor("flow"); QColor arrowColorDown = ConfigColor("flow");
QColor arrowColorUp = ConfigColor("cflow");
QPainter p(this); QPainter p(this);
QPen pen(arrowColor, 1, Qt::SolidLine, Qt::FlatCap, Qt::RoundJoin); QPen penDown(arrowColorDown, penSizePix, Qt::SolidLine, Qt::FlatCap, Qt::RoundJoin);
p.setPen(pen); QPen penUp(arrowColorUp, penSizePix, Qt::SolidLine, Qt::FlatCap, Qt::RoundJoin);
// Fill background
p.fillRect(event->rect(), Config()->getColor("gui.background").darker(115));
QList<DisassemblyLine> lines = disas->getLines(); QList<DisassemblyLine> lines = disas->getLines();
// Precompute pixel position of the arrows
// TODO This can probably be done in another loop for performance purposes
QMap<RVA, int> linesPixPosition; QMap<RVA, int> linesPixPosition;
int i = 0; QMap<RVA, pair<RVA, int>> arrowInfo; /* offset -> (arrow, layer of arrow) */
int baseOffset = lineHeight / 2; int nLines = 0;
for (auto l : lines) { for (const auto& line : lines) {
linesPixPosition[l.offset] = i * lineHeight + baseOffset; linesPixPosition[line.offset] = nLines * lineHeight + lineHeight / 2 + topOffset;
i++; nLines++;
if (line.arrow != RVA_INVALID) {
arrowInfo.insert(line.offset, { line.arrow, -1 });
}
} }
for (auto it = arrowInfo.begin(); it != arrowInfo.end(); it++) {
Range currRange = { it.key(), it.value().first };
it.value().second = it.value().second == -1
? 1
: it.value().second;
for (auto innerIt = arrowInfo.begin(); innerIt != arrowInfo.end(); innerIt++) {
if (innerIt == it) {
continue;
}
Range innerRange = { innerIt.key(), innerIt.value().first };
if (currRange.contains(innerRange) || currRange.contains(innerRange.from)) {
it.value().second++;
}
}
}
// I'm sorry this loop below, but it is only way I see how to implement the feature
while (true) {
bool correction = false;
bool correction2 = false;
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 (it.value().second == innerIt.value().second &&
(currRange.contains(innerRange) || currRange.contains(innerRange.from))) {
it.value().second++;
correction = true;
}
int distance = it.value().second - innerIt.value().second;
if (distance > 0 && distance < minDistance) {
minDistance = distance;
}
}
if (minDistance > 1 && minDistance != INT32_MAX) {
correction2 = true;
it.value().second -= minDistance - 1;
}
}
if (!correction && !correction2) {
break;
}
}
const RVA currOffset = disas->getSeekable()->getOffset();
// Draw the lines // Draw the lines
// TODO Use better var names for (const auto& l : lines) {
QPolygon arrow; int lineOffset = int((distanceBetweenLines * arrowInfo[l.offset].second + distanceBetweenLines) *
int direction; p.device()->devicePixelRatioF());
int lineFinalHeight;
int lineOffset = 10;
for (auto l : lines) {
// Skip until we reach a line that jumps to a destination // Skip until we reach a line that jumps to a destination
if (l.arrow == 0) { if (l.arrow == RVA_INVALID) {
continue; continue;
} }
// Compute useful variables bool jumpDown = l.arrow > l.offset;
if (l.arrow > currentOffset) { p.setPen(jumpDown ? penDown : penUp);
direction = 1; if (l.offset == currOffset) {
} else { QPen pen = p.pen();
direction = -1; pen.setWidth((penSizePix * 3) / 2);
p.setPen(pen);
} }
bool endVisible = true; bool endVisible = true;
int currentLineYPos = linesPixPosition[l.offset];
lineFinalHeight = linesPixPosition.value(l.arrow, -1);
if (lineFinalHeight == -1) { int currentLineYPos = linesPixPosition[l.offset];
if (direction == 1) { int lineArrowY = linesPixPosition.value(l.arrow, -1);
lineFinalHeight = 0;
} else { if (lineArrowY == -1) {
lineFinalHeight = size().height(); lineArrowY = jumpDown
} ? geometry().bottom()
: 0;
endVisible = false; endVisible = false;
} }
// Draw the lines // Draw the lines
p.drawLine(rightOffset, currentLineYPos, rightOffset - lineOffset, currentLineYPos); p.drawLine(rightOffset, currentLineYPos, rightOffset - lineOffset, currentLineYPos);
p.drawLine(rightOffset - lineOffset, currentLineYPos, rightOffset - lineOffset, lineFinalHeight); p.drawLine(rightOffset - lineOffset, currentLineYPos, rightOffset - lineOffset, lineArrowY);
if (endVisible) { if (endVisible) {
p.drawLine(rightOffset - lineOffset, lineFinalHeight, rightOffset, lineFinalHeight); p.drawLine(rightOffset - lineOffset, lineArrowY, rightOffset, lineArrowY);
// Draw the arrow QPainterPath arrow;
arrow.clear(); arrow.moveTo(rightOffset - arrowWidth, lineArrowY + arrowWidth);
arrow.append(QPoint(rightOffset - 3, lineFinalHeight + 3)); arrow.lineTo(rightOffset - arrowWidth, lineArrowY - arrowWidth);
arrow.append(QPoint(rightOffset - 3, lineFinalHeight - 3)); arrow.lineTo(rightOffset, lineArrowY);
arrow.append(QPoint(rightOffset, lineFinalHeight)); p.fillPath(arrow, p.pen().brush());
} }
p.drawConvexPolygon(arrow);
// Shift next jump line
lineOffset += 10;
} }
} }