Added viewer to show jumps in disassembly viewer

This commit is contained in:
Mark Long 2016-10-31 11:19:44 -05:00
parent 93f1b143d3
commit e85b95b66b
10 changed files with 745 additions and 51 deletions

View File

@ -66,7 +66,9 @@ SOURCES += \
src/binaryfile/AssemblerSymbolModel.cpp \
src/ui/diskexplorer/DiskExplorer.cpp \
src/ui/diskexplorer/DiskExplorerMapWidget.cpp \
src/applesoftfile/ApplesoftRetokenizer.cpp
src/applesoftfile/ApplesoftRetokenizer.cpp \
src/internals/JumpLineManager.cpp \
src/ui/widgets/FlowLineTextBrowser.cpp
HEADERS += \
@ -116,7 +118,9 @@ HEADERS += \
src/ui/diskexplorer/DiskExplorer.h \
src/ui/diskexplorer/DiskExplorerMapWidget.h \
src/applesoftfile/ApplesoftRetokenizer.h \
src/util/AppleColors.h
src/util/AppleColors.h \
src/internals/JumpLineManager.h \
src/ui/widgets/FlowLineTextBrowser.h
FORMS += \
src/ui/catalogwidget.ui \

View File

@ -15,6 +15,8 @@ Disassembler::Disassembler(QByteArray memimage)
QList<DisassembledItem> Disassembler::disassemble(quint16 from, quint16 to,
QList<quint16> entryPoints,
bool processRecursively) {
m_from = from;
m_to = to;
QList<DisassembledItem> retval;
qDebug() << "\n\n*****************\n\nDisassemble: From"<<uint16ToHex(from)<<"to"<<uint16ToHex(to);
//#define OLDDISSEM
@ -64,12 +66,12 @@ QList<DisassembledItem> Disassembler::disassemble(quint16 from, quint16 to,
{
qDebug() << "Is Branch";
m_stack.push(item.targetAddress());
// if (!m_jumps.contains(item.targetAddress()))
// {
// m_jumps.append(item.targetAddress());
// qDebug() << "Appending branch" << uint16ToHex(item.targetAddress()) << "to jump table";
// if (!m_jumps.contains(item.targetAddress()))
// {
// m_jumps.append(item.targetAddress());
// qDebug() << "Appending branch" << uint16ToHex(item.targetAddress()) << "to jump table";
// }
// }
}
if (item.isJsr() && !item.canNotFollow())
@ -77,7 +79,7 @@ QList<DisassembledItem> Disassembler::disassemble(quint16 from, quint16 to,
if (item.targetAddress() <= to) //TODO: Remove this to not limit disassembly to program range
{
if (m_stack.push(item.targetAddress()))
//if (!m_jumps.contains(item.targetAddress()))
//if (!m_jumps.contains(item.targetAddress()))
{
qDebug() << "Appending" << uint16ToHex(item.targetAddress()) << "to jump table";
@ -148,6 +150,12 @@ QList<DisassembledItem> Disassembler::disassemble(quint16 from, quint16 to,
// }
// qDebug() << "Operations Args:" << hexdump;
if (processRecursively)
{
// m_jlm.dumpJumps();
m_jumplines = m_jlm.buildJumpLines();
qDebug() << "Num Channels: " << m_jlm.getNumJumpLineChannels();
}
return retval;
}
@ -165,7 +173,10 @@ bool Disassembler::disassembleOp(quint16 address, DisassembledItem &retval, Memo
retval.setInstruction(op);
if (opcode == 0x6C || opcode == 0x7c) // Indirect jumps
{
m_jlm.addJump(address,address,IsUnknownJump,m_from,m_to);
retval.setCanNotFollow(true);
}
QString disassemblyLine;
QString hexValueString;
@ -254,6 +265,10 @@ bool Disassembler::disassembleOp(quint16 address, DisassembledItem &retval, Memo
quint16 offsetAddress = address+2+offset;
retval.setTargetAddress(offsetAddress);
if (opcode == 0x80) // BRA
m_jlm.addJump(address,offsetAddress,IsBRA,m_from,m_to);
else
m_jlm.addJump(address,offsetAddress,IsBranch,m_from,m_to);
disassemblyLine = QString("%1 _ARG16_ {%2%3}").arg(op.mnemonic())
.arg((offset<0)?"-":"+")
@ -301,16 +316,19 @@ bool Disassembler::disassembleOp(quint16 address, DisassembledItem &retval, Memo
if (opcode == 0x20 || opcode == 0x4c) {
retval.setTargetAddress(makeWord(hexValues[1],hexValues[2]));
}
if (opcode == 0x4c)
{
qDebug() << "JMP: Adding flow address "
<< uint16ToHex(makeWord(hexValues[1],hexValues[2])) << "to jump table";
m_stack.push(argval);
retval.setCanNotFollow(true);
if (opcode == 0x4c) // JMP
{
qDebug() << "JMP: Adding flow address "
<< uint16ToHex(makeWord(hexValues[1],hexValues[2])) << "to jump table";
m_jlm.addJump(address,argval,IsJMP,m_from,m_to);
m_stack.push(argval);
retval.setCanNotFollow(true);
}
else // JSR
{
m_jlm.addJump(address,argval,IsJSR,m_from,m_to);
}
}
retval.setAddress(address);

View File

@ -3,6 +3,7 @@
#include "MemoryUsageMap.h"
#include "util.h"
#include "JumpLineManager.h"
#include <QByteArray>
#include <QStringList>
@ -10,6 +11,7 @@
#include <QDebug>
#include <QStack>
enum AddressMode {
AM_InvalidOp,
AM_Absolute, // a
@ -163,6 +165,9 @@ private:
class Disassembler
{
public:
Disassembler(QByteArray memimage);
enum ProcessorType {
@ -184,12 +189,16 @@ public:
return m_opcodeinfo[opcode].mnemonic();
}
private:
JumpLines getJumpLines() const { return m_jumplines; }
private:
bool disassembleOp(quint16 address, DisassembledItem &retval, MemoryUsageMap *memuse = Q_NULLPTR);
void makeOpcodeTable();
quint16 m_from;
quint16 m_to;
QHash<quint8,AssyInstruction> m_opcodeinfo;
QByteArray m_memimage;
@ -197,6 +206,10 @@ private:
MemoryUsageMap m_memusagemap;
JumpLineManager m_jlm;
JumpLines m_jumplines;
};
#endif // DISASSEMBLER_H

View File

@ -0,0 +1,173 @@
#include "JumpLineManager.h"
JumpLineManager::JumpLineManager(quint16 from, quint16 to)
: m_start(from),
m_end(to)
{
qDebug() << "JumpLineManager(from:"<<uint16ToHex(m_start)<<", to:"<<uint16ToHex(m_end) << ")";
}
void JumpLineManager::addJump(quint16 src, quint16 dest, JumpType type, quint16 from, quint16 to)
{
TJump jump(src,dest);
if (src >= from && src <= to && dest >= from && dest <= to)
{
if (!m_jumpmap.contains(jump))
{
// qDebug() << "JumpLineManager::addJump: Added Jump from" << uint16ToHex(src) << "to" << uint16ToHex(dest) << ", Type:" << type;
m_jumpmap.insert(jump,type);
}
else
{
qDebug() << "JumpLineManager::addJump: Not adding duplicate jump:" << uint16ToHex(src) << "," << uint16ToHex(dest);
}
}
else
{
qDebug() << "JumpLineManager::addJump: Address range is out of bounds";
}
}
void JumpLineManager::dumpJumps() const {
dumpJumps(m_jumpmap);
}
void JumpLineManager::dumpJumps(JumpMap map) const
{
qDebug() << "JumpLineManager::dumpJumps()\n JumpTable:";
QMapIterator<TJump,JumpType> it(map);
while (it.hasNext())
{
it.next();
QString jumptypelabel;
if (it.value() == IsUnknownJump) { jumptypelabel = "Unknown Jump"; }
if (it.value() == IsJMP) { jumptypelabel = "JMP"; }
if (it.value() == IsBranch) { jumptypelabel = "Branch"; }
if (it.value() == IsJSR) { jumptypelabel = "JSR"; }
if (it.value() == IsBRA) { jumptypelabel = "BRA"; }
qDebug() << " Jump from" << uint16ToHex(it.key().first) << "to"
<< uint16ToHex(it.key().second) << jumptypelabel;
}
}
JumpLines JumpLineManager::buildJumpLines()
{
qDebug() << "A";
m_channelsAtAddress.clear();
m_jumplines.m_maxChannel = 0;
QMapIterator<TJump, JumpType> it(m_jumpmap);
while (it.hasNext())
{
it.next();
TJump range = it.key();
JumpLine jl;
jl.type = it.value();
jl.from = range.first;
jl.to = range.second;
int channel = findBestChannel(jl);
setChannelForJumpLine(channel,jl);
m_jumplines.jumpLines.append(jl);
m_jumplines.m_maxChannel = qMax(m_jumplines.m_maxChannel, channel);
}
qDebug() << "A";
return m_jumplines;
}
int JumpLineManager::findBestChannel(JumpLine &jl)
{
qDebug() << "findBestChannel()";
if (m_jumplines.jumpLines.count() == 0)
{
return 0;
}
int potentialChannel = 0;
bool foundChannel = false;
while (!foundChannel)
{
qDebug() << "Tryning potential channel" << potentialChannel;
bool matched = false;
for (quint16 addr = jl.min(); addr <= jl.max(); addr++)
{
// If any of these addresses contain the potential channel, move on
if (m_channelsAtAddress[addr].contains(potentialChannel))
{
matched = true;
}
}
if (matched)
{
potentialChannel++;
matched = false;
}
else
{
foundChannel = true;
}
}
return potentialChannel;
}
void JumpLineManager::setChannelForJumpLine(int channel, JumpLine &jl)
{
jl.channel = channel;
for (quint16 addr = jl.min(); addr <= jl.max(); addr++)
{
m_channelsAtAddress[addr].append(channel);
}
}
void JumpLineManager::dumpJumpLines() const
{
foreach (JumpLine jl, m_jumplines.jumpLines)
{
qDebug() << " JumpLine from:" << uint16ToHex(jl.from)
<< " to:" << uint16ToHex(jl.to)
<< "channel: " << jl.channel << " type:" << jl.type;
}
}
bool JumpLineManager::doJumpsIntersect(TJump &A, TJump &B) const
{
if (A == B) return false;
if (isLineWithinRange(A.first,B) || isLineWithinRange(A.second,B))
return true;
if (isLineWithinRange(B.first,A) || isLineWithinRange(B.second,A))
return true;
return false;
}
bool JumpLineManager::isLineWithinRange(quint16 line, TJump &jm) const
{
quint16 min = qMin(jm.first,jm.second);
quint16 max = qMax(jm.first,jm.second);
return (line > min && line < max);
}
QList<JumpLine> JumpLines::jumpLinesAtAddress(quint16 addrs)
{
QList<JumpLine> list;
foreach (JumpLine jl, jumpLines)
{
if (addrs >= jl.min() && addrs <= jl.max())
{
list.append(jl);
}
}
return list;
}

View File

@ -0,0 +1,102 @@
#ifndef JUMPLINEMANAGER_H
#define JUMPLINEMANAGER_H
#include <QPair>
#include <QMap>
#include <QMapIterator>
#include <QDebug>
#include "util.h"
typedef QPair<quint16,quint16> TJump;
typedef enum {
IsUnknownJump,
IsJMP,
IsBranch,
IsJSR,
IsBRA
} JumpType;
typedef QMap<TJump,JumpType> JumpMap;
class JumpLine {
public:
JumpLine() : from(0), to(0), channel(-1), type(IsUnknownJump) { }
quint16 from;
quint16 to;
int channel;
JumpType type;
inline quint16 min() const { return qMin(from,to); }
inline quint16 max() const { return qMax(from,to); }
inline bool isUp() const { return (from > to); }
inline bool isDown() const { return !isUp(); }
};
//////////////////
class JumpLines
{
public:
JumpLines() : m_maxChannel(0)
{
}
QList<int> channelsAtAddress(quint16 address)
{
return m_channelsAtAddress[address];
}
QList<JumpLine> jumpLinesAtAddress(quint16 addrs);
QList<JumpLine> jumpLines;
int m_maxChannel;
QMap<quint16, QList<int> > m_channelsAtAddress;
};
//////////////////
class JumpLineManager
{
public:
JumpLineManager(quint16 from = 0x0000, quint16 to = 0xffff);
void addJump(quint16 src, quint16 dest, JumpType type,quint16 from = 0, quint16 to = 0xffff);
void dumpJumps() const;
JumpLines buildJumpLines();
JumpLines getJumpLines() const { return m_jumplines; }
void clear() { m_jumpmap.clear(); }
void dumpJumpLines() const;
int getNumJumpLineChannels() const { return m_jumplines.m_maxChannel; }
protected:
int findBestChannel(JumpLine &jl);
void setChannelForJumpLine(int channel, JumpLine &jl);
private:
quint16 m_start;
quint16 m_end;
JumpMap m_jumpmap;
JumpLines m_jumplines;
QMap<quint16, QList<int>> m_channelsAtAddress;
bool doJumpsIntersect(TJump &A, TJump &B) const;
bool isLineWithinRange(quint16 line, TJump &jm) const;
void dumpJumps(JumpMap map) const;
};
#endif // JUMPLINEMANAGER_H

View File

@ -104,8 +104,8 @@ void DisassemblerViewer::handleDisassembleRequest(QList<quint16> addresses)
{
QStringList strings;
strings += getDisassemblyStrings(addresses);
disassemble(addresses);
strings += getDisassemblyStrings();
qSort(strings);
strings.removeDuplicates();
@ -126,7 +126,11 @@ void DisassemblerViewer::handleDisassembleRequest(QList<quint16> addresses)
}
QStringList DisassemblerViewer::getDisassemblyStrings(QList<quint16> entryPoints) {
QStringList DisassemblerViewer::getDisassemblyStrings() {
return m_disassemblyStrings;
}
void DisassemblerViewer::disassemble(QList<quint16> entryPoints) {
Disassembler dis(m_mem.values());
@ -136,6 +140,7 @@ QStringList DisassemblerViewer::getDisassemblyStrings(QList<quint16> entryPoints
m_file->address()+length,
entryPoints);
dis.setUnknownToData(m_file->address(),m_file->address()+length);
m_jumpLines = dis.getJumpLines();
QStringList formattedLines;
@ -168,15 +173,15 @@ QStringList DisassemblerViewer::getDisassemblyStrings(QList<quint16> entryPoints
if (dis.memoryUsageMap()->at(idx).testFlag(Data))
{
QString newline = QString("%1: %2 %3 (%4)").arg(uint16ToHex(idx))
.arg(uint8ToHex(m_mem.at(idx)))
.arg(makeDescriptorStringForVal(m_mem.at(idx)))
.arg(dis.getMnemonicForOp(m_mem.at(idx)));
.arg(uint8ToHex(m_mem.at(idx)))
.arg(makeDescriptorStringForVal(m_mem.at(idx)))
.arg(dis.getMnemonicForOp(m_mem.at(idx)));
formattedLines.append(newline);
}
}
qSort(formattedLines);
return formattedLines;
m_disassemblyStrings = formattedLines;
}
@ -1500,12 +1505,9 @@ void DisassemblerViewer::showMetadataDialog()
void DisassemblerViewer::setData(QByteArray data)
{
ui->textArea->setText(data);
ui->textArea->setJumpLines(&m_jumpLines);
}
void DisassemblerViewer::setText(QString text)
{
ui->textArea->setHtml(text);
}
bool DisassemblerViewer::canPrint() const { return true; }
@ -1556,30 +1558,30 @@ QString DisassemblerViewer::makeDescriptorStringForVal(quint8 val)
{
QString retval;
// QString zone;
// if (val <= 0x3f) zone = "Inverse";
// else if (val <= 0x7f) zone = "Flash";
// else if (val <= 0x9f) zone = "(Alt) Normal";
// else zone = "Normal";
// QString zone;
// if (val <= 0x3f) zone = "Inverse";
// else if (val <= 0x7f) zone = "Flash";
// else if (val <= 0x9f) zone = "(Alt) Normal";
// else zone = "Normal";
// quint8 baseascii = val;
// if (val <= 0x1f) baseascii += 0x40;
// else if (val <= 0x5f) baseascii += 0;
// else if (val <= 0xbf) baseascii -= 0x40;
// else baseascii -= 80;
// quint8 baseascii = val;
// if (val <= 0x1f) baseascii += 0x40;
// else if (val <= 0x5f) baseascii += 0;
// else if (val <= 0xbf) baseascii -= 0x40;
// else baseascii -= 80;
// QString ch = QChar(baseascii);
// if (val == 0xff) ch = "[DEL]";
// QString ch = QChar(baseascii);
// if (val == 0xff) ch = "[DEL]";
// QString appleAscii = QString("%1 %2").arg(ch).arg(zone);
// QString appleAscii = QString("%1 %2").arg(ch).arg(zone);
// if (val < 0x20)
// {
// QString ctrl = QString(" / (^%1)").arg(QChar(val+0x40));
// appleAscii.append(ctrl);
// }
// if (val < 0x20)
// {
// QString ctrl = QString(" / (^%1)").arg(QChar(val+0x40));
// appleAscii.append(ctrl);
// }
// retval = QString("; %1 / %2").arg(val).arg(appleAscii);
// retval = QString("; %1 / %2").arg(val).arg(appleAscii);
retval = QString("; %1").arg(val);
return retval;
}

View File

@ -9,6 +9,7 @@
#include "relocatablefile.h"
#include "fileviewerinterface.h"
#include "DisassemblerMetadataDialog.h"
#include "JumpLineManager.h"
namespace Ui {
class DisassemblerViewer;
@ -25,7 +26,6 @@ public:
void setFile(BinaryFile *file);
void setFile(RelocatableFile *file);
void setData(QByteArray data);
void setText(QString text);
QString getPotentialLabel(quint16 address);
virtual bool optionsMenuItems(QMenu *);
@ -35,7 +35,8 @@ public:
QString makeDescriptorStringForVal(quint8 val);
QStringList getDisassemblyStrings(QList<quint16> entryPoints);
void disassemble(QList<quint16> entryPoints);
QStringList getDisassemblyStrings();
public slots:
void setFile(GenericFile *file);
@ -60,6 +61,9 @@ private:
Memory m_mem;
bool m_isRelo;
QStringList m_disassemblyStrings;
JumpLines m_jumpLines;
};
#endif // DISASSEMBLERVIEWER_H

View File

@ -15,7 +15,7 @@
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QTextBrowser" name="textArea">
<widget class="FlowLineTextBrowser" name="textArea">
<property name="font">
<font>
<family>Courier</family>
@ -36,6 +36,11 @@
<header>viewerbase.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>FlowLineTextBrowser</class>
<extends>QTextBrowser</extends>
<header>FlowLineTextBrowser.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>

View File

@ -0,0 +1,299 @@
#include "FlowLineTextBrowser.h"
#include <QPainter>
#include <QTextBlock>
#include <QRegularExpression>
#include <QTextDocument>
#include <QTextCursor>
#include <QAbstractTextDocumentLayout>
#include <QScrollBar>
FlowLineTextBrowser::FlowLineTextBrowser(QWidget *parent) : QTextBrowser(parent)
{
m_lineArea = new LineArea(this);
m_jl = Q_NULLPTR;
//this->verticalScrollBar()->setSliderPosition(this->verticalScrollBar()->sliderPosition());
// connect(this->document(), SIGNAL(blockCountChanged(int)), SLOT(updateLineAreaWidth(int)));
connect(this, SIGNAL(updateRequest(QRect,int)), SLOT(updateLineArea(QRect,int)));
updateLineAreaWidth();
}
int FlowLineTextBrowser::getFirstVisibleBlock(QTextBlock *firstBlock) const
{
QTextCursor curs = QTextCursor(this->document());
curs.movePosition(QTextCursor::Start);
for(int i=0; i < this->document()->blockCount(); ++i)
{
QTextBlock block = curs.block();
QRect r1 = this->viewport()->geometry();
QRect r2 = this->document()->documentLayout()->blockBoundingRect(block).translated(
this->viewport()->geometry().x(), this->viewport()->geometry().y() - (
this->verticalScrollBar()->sliderPosition()
) ).toRect();
if (r1.contains(r2, true) || r1.intersects(r2))
{
qDebug() << r2;
if (firstBlock)
*firstBlock = block;
return i;
}
curs.movePosition(QTextCursor::NextBlock);
}
if (firstBlock)
*firstBlock = QTextBlock();
return 0;
}
void FlowLineTextBrowser::showEvent(QShowEvent *)
{
this->verticalScrollBar()->setSliderPosition(this->verticalScrollBar()->sliderPosition());
}
void FlowLineTextBrowser::lineAreaPaintEvent(QPaintEvent *event)
{
QPainter painter(m_lineArea);
painter.fillRect(event->rect(), Qt::lightGray);
painter.setPen(Qt::red);
painter.setBrush(Qt::red);
QTextBlock block;
getFirstVisibleBlock(&block);
qDebug() << block.text();
bool foundFirst = false;
quint16 linenum;
QTextCursor curs = QTextCursor(this->document());
curs.movePosition(QTextCursor::Start);
bool inBlankLine = false;
// for each visible block
while (isBlockVisible(block) && block.next().isValid())
{
QString text = block.text();
if (text.contains(QRegularExpression("[0-9A-Fa-f]{4}:")))
{
bool ok = true;
quint16 addr = (quint16) text.left(4).toInt(&ok,16);
if (ok)
{
// qDebug() << "Addr:" << uint16ToHex(addr);
linenum = addr;
foundFirst = true;
inBlankLine = false;
}
else
{
// qDebug() << "No address!";
}
}
else
{
inBlankLine = true;
}
// qDebug() << "Channels:" << m_jl->channelsAtAddress(linenum);
if (foundFirst)
{
QRect blockRect = getBlockGeometry(block);
float topInY = blockRect.top() + ((1./6.) * blockRect.height());
float topOutY = blockRect.top() + ((2./6.) * blockRect.height());
float botOutY = blockRect.top() + ((3./6.) * blockRect.height());
float botInY = blockRect.top() + ((4./6.) * blockRect.height());
// painter.drawLine(0,blockRect.top(),m_lineArea->width(),blockRect.bottom());
QList<JumpLine> jllist = m_jl->jumpLinesAtAddress(linenum);
foreach (JumpLine jl, jllist)
{
int offset = getChannelOffset(jl.channel);
if (!inBlankLine)
{
if (jl.from == linenum)
{
if (jl.from == jl.to)
{
painter.drawLine(offset, botInY,
m_lineArea->width(), botInY);
painter.drawLine(offset, botInY, offset, topOutY);
painter.drawLine(offset, topOutY,
m_lineArea->width(), topOutY);
// painter.drawEllipse(QPointF(m_lineArea->width(),topOutY),2,2);
}
else
{
if (jl.isUp())
{
painter.drawLine(offset, topOutY,
offset, blockRect.top());
painter.drawLine(offset, topOutY,
m_lineArea->width(), topOutY);
painter.drawEllipse(QPointF(m_lineArea->width(),topOutY),2,2);
}
else
{
painter.drawLine(offset, botOutY,
offset, blockRect.bottom());
painter.drawLine(offset, botOutY,
m_lineArea->width(), botOutY);
// painter.drawEllipse(QPointF(m_lineArea->width(),botOutY),2,2);
}
}
}
else if (jl.to == linenum)
{
if (jl.isUp())
{
painter.drawLine(offset, botInY,
offset, blockRect.bottom());
painter.drawLine(offset, botInY,
m_lineArea->width(), botInY);
QPolygonF varrow;
varrow.append(QPointF(offset, botInY));
varrow.append(QPointF(offset-3, botInY+5));
varrow.append(QPointF(offset+3, botInY+5));
painter.drawConvexPolygon(varrow);
QPolygonF harrow;
harrow.append(QPointF(m_lineArea->width(),botInY));
harrow.append(QPointF(m_lineArea->width()-4,botInY-3));
harrow.append(QPointF(m_lineArea->width()-4,botInY+3));
painter.drawConvexPolygon(harrow);
}
else
{
painter.drawLine(offset, topInY,
offset, blockRect.top());
painter.drawLine(offset, topInY,
m_lineArea->width(), topInY);
QPolygonF varrow;
varrow.append(QPointF(offset, topInY));
varrow.append(QPointF(offset-3, topInY-5));
varrow.append(QPointF(offset+3, topInY-5));
painter.drawConvexPolygon(varrow);
QPolygonF harrow;
harrow.append(QPointF(m_lineArea->width(),topInY));
harrow.append(QPointF(m_lineArea->width()-4,topInY-3));
harrow.append(QPointF(m_lineArea->width()-4,topInY+3));
painter.drawConvexPolygon(harrow);
}
}
if (linenum > jl.min() && linenum < jl.max())
{
painter.drawLine(offset, blockRect.top(),offset, blockRect.bottom());
}
}
else // inBlankLine
{
if (jl.from == linenum)
{
if (jl.from != jl.to)
{
if (jl.isDown())
{
painter.drawLine(offset, blockRect.top(),offset, blockRect.bottom());
}
}
}
else if (jl.to == linenum)
{
if (jl.isUp())
{
painter.drawLine(offset, blockRect.top(),offset, blockRect.bottom());
}
}
if (linenum > jl.min() && linenum < jl.max())
{
painter.drawLine(offset, blockRect.top(),offset, blockRect.bottom());
}
}
}
}
block = block.next();
}
}
QRect FlowLineTextBrowser::getBlockGeometry(QTextBlock block) const
{
QRect rect = this->document()->documentLayout()->blockBoundingRect(block).translated(
this->viewport()->geometry().x(), this->viewport()->geometry().y() - (
this->verticalScrollBar()->sliderPosition()
) ).toRect();
return rect;
}
bool FlowLineTextBrowser::isBlockVisible(QTextBlock block) const
{
QRect r1 = this->viewport()->geometry();
QRect r2 = this->document()->documentLayout()->blockBoundingRect(block).translated(
this->viewport()->geometry().x(), this->viewport()->geometry().y() - (
this->verticalScrollBar()->sliderPosition()
) ).toRect();
return (r1.contains(r2, true) || r1.intersects(r2));
}
int FlowLineTextBrowser::getChannelOffset(int channel)
{
int width = lineAreaWidth();
return width - (channel * 9)- 10;
}
int FlowLineTextBrowser::lineAreaWidth()
{
if (!m_jl) return 10;
return m_jl->m_maxChannel* 9 + 20;
}
void FlowLineTextBrowser::setJumpLines(JumpLines *jl)
{
m_jl = jl;
m_lineArea->setJumpLines(jl);
updateLineAreaWidth();
}
void FlowLineTextBrowser::paintEvent(QPaintEvent *event)
{
m_lineArea->update();
QTextBrowser::paintEvent(event);
}
void FlowLineTextBrowser::resizeEvent(QResizeEvent *event)
{
QTextBrowser::resizeEvent(event);
QRect cr = contentsRect();
m_lineArea->setGeometry(QRect(cr.left(), cr.top(), lineAreaWidth(), cr.height()));
}
void FlowLineTextBrowser::updateLineAreaWidth()
{
setViewportMargins(lineAreaWidth(),0,0,0);
}
void FlowLineTextBrowser::updateLineArea(const QRect &rect, int dy)
{
if (dy)
m_lineArea->scroll(0, dy);
else
m_lineArea->update(0, rect.y(), m_lineArea->width(), rect.height());
if (rect.contains(viewport()->rect()))
updateLineAreaWidth();
}

View File

@ -0,0 +1,74 @@
#ifndef FLOWLINETEXTBROWSER_H
#define FLOWLINETEXTBROWSER_H
#include "JumpLineManager.h"
#include <QTextBrowser>
#include <QObject>
#include <QResizeEvent>
#include <QDebug>
#include <QPaintEvent>
#include <QResizeEvent>
#include <QSize>
#include <QWidget>
#include <QScrollEvent>
class LineArea;
class FlowLineTextBrowser : public QTextBrowser
{
public:
FlowLineTextBrowser(QWidget *parent = Q_NULLPTR);
void lineAreaPaintEvent(QPaintEvent *event);
int lineAreaWidth();
void setJumpLines(JumpLines *jl);
protected:
void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE;
void resizeEvent(QResizeEvent *event) Q_DECL_OVERRIDE;
int getFirstVisibleBlock(QTextBlock *firstBlock) const;
int getChannelOffset(int channel);
QRect getBlockGeometry(QTextBlock block) const;
bool isBlockVisible(QTextBlock block) const;
void showEvent(QShowEvent *) Q_DECL_OVERRIDE;
private slots:
void updateLineAreaWidth();
void updateLineArea(const QRect &, int);
private:
LineArea *m_lineArea;
JumpLines *m_jl;
};
class LineArea : public QWidget
{
public:
LineArea(FlowLineTextBrowser *browser) : QWidget(browser)
{
m_browser = browser;
}
void setJumpLines(JumpLines *jl) { m_jl = jl; }
QSize sizeHint() const Q_DECL_OVERRIDE
{
return QSize(m_browser->lineAreaWidth(),0);
}
protected:
void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE
{
m_browser->lineAreaPaintEvent(event);
}
private:
FlowLineTextBrowser *m_browser;
JumpLines *m_jl;
};
#endif // FLOWLINETEXTBROWSER_H