diff --git a/AppleSAWS.pro b/AppleSAWS.pro index c04e77a..a20c2de 100644 --- a/AppleSAWS.pro +++ b/AppleSAWS.pro @@ -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 \ diff --git a/src/binaryfile/disassembler.cxx b/src/binaryfile/disassembler.cxx index f27649b..9b9664a 100644 --- a/src/binaryfile/disassembler.cxx +++ b/src/binaryfile/disassembler.cxx @@ -15,6 +15,8 @@ Disassembler::Disassembler(QByteArray memimage) QList Disassembler::disassemble(quint16 from, quint16 to, QList entryPoints, bool processRecursively) { + m_from = from; + m_to = to; QList retval; qDebug() << "\n\n*****************\n\nDisassemble: From"< 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 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 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); diff --git a/src/binaryfile/disassembler.h b/src/binaryfile/disassembler.h index e9b49e9..b634238 100644 --- a/src/binaryfile/disassembler.h +++ b/src/binaryfile/disassembler.h @@ -3,6 +3,7 @@ #include "MemoryUsageMap.h" #include "util.h" +#include "JumpLineManager.h" #include #include @@ -10,6 +11,7 @@ #include #include + 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 m_opcodeinfo; QByteArray m_memimage; @@ -197,6 +206,10 @@ private: MemoryUsageMap m_memusagemap; + JumpLineManager m_jlm; + + JumpLines m_jumplines; + }; #endif // DISASSEMBLER_H diff --git a/src/internals/JumpLineManager.cpp b/src/internals/JumpLineManager.cpp new file mode 100644 index 0000000..74e5195 --- /dev/null +++ b/src/internals/JumpLineManager.cpp @@ -0,0 +1,173 @@ +#include "JumpLineManager.h" + +JumpLineManager::JumpLineManager(quint16 from, quint16 to) + : m_start(from), + m_end(to) +{ + qDebug() << "JumpLineManager(from:"<= 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 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 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 JumpLines::jumpLinesAtAddress(quint16 addrs) +{ + QList list; + foreach (JumpLine jl, jumpLines) + { + if (addrs >= jl.min() && addrs <= jl.max()) + { + list.append(jl); + } + } + return list; +} diff --git a/src/internals/JumpLineManager.h b/src/internals/JumpLineManager.h new file mode 100644 index 0000000..6e14605 --- /dev/null +++ b/src/internals/JumpLineManager.h @@ -0,0 +1,102 @@ +#ifndef JUMPLINEMANAGER_H +#define JUMPLINEMANAGER_H + +#include +#include +#include +#include + +#include "util.h" + +typedef QPair TJump; +typedef enum { + IsUnknownJump, + IsJMP, + IsBranch, + IsJSR, + IsBRA +} JumpType; +typedef QMap 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 channelsAtAddress(quint16 address) + { + return m_channelsAtAddress[address]; + } + + QList jumpLinesAtAddress(quint16 addrs); + + QList jumpLines; + + int m_maxChannel; + + QMap > 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> m_channelsAtAddress; + + + bool doJumpsIntersect(TJump &A, TJump &B) const; + bool isLineWithinRange(quint16 line, TJump &jm) const; + + void dumpJumps(JumpMap map) const; + +}; + +#endif // JUMPLINEMANAGER_H diff --git a/src/ui/viewers/disassemblerviewer.cpp b/src/ui/viewers/disassemblerviewer.cpp index 6801ea0..c34c4e2 100644 --- a/src/ui/viewers/disassemblerviewer.cpp +++ b/src/ui/viewers/disassemblerviewer.cpp @@ -104,8 +104,8 @@ void DisassemblerViewer::handleDisassembleRequest(QList addresses) { QStringList strings; - strings += getDisassemblyStrings(addresses); - + disassemble(addresses); + strings += getDisassemblyStrings(); qSort(strings); strings.removeDuplicates(); @@ -126,7 +126,11 @@ void DisassemblerViewer::handleDisassembleRequest(QList addresses) } -QStringList DisassemblerViewer::getDisassemblyStrings(QList entryPoints) { +QStringList DisassemblerViewer::getDisassemblyStrings() { + return m_disassemblyStrings; +} + +void DisassemblerViewer::disassemble(QList entryPoints) { Disassembler dis(m_mem.values()); @@ -136,6 +140,7 @@ QStringList DisassemblerViewer::getDisassemblyStrings(QList 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 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; } diff --git a/src/ui/viewers/disassemblerviewer.h b/src/ui/viewers/disassemblerviewer.h index 98fecf7..3422433 100644 --- a/src/ui/viewers/disassemblerviewer.h +++ b/src/ui/viewers/disassemblerviewer.h @@ -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 entryPoints); + void disassemble(QList 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 diff --git a/src/ui/viewers/disassemblerviewer.ui b/src/ui/viewers/disassemblerviewer.ui index f374e85..10e08ab 100644 --- a/src/ui/viewers/disassemblerviewer.ui +++ b/src/ui/viewers/disassemblerviewer.ui @@ -15,7 +15,7 @@ - + Courier @@ -36,6 +36,11 @@
viewerbase.h
1 + + FlowLineTextBrowser + QTextBrowser +
FlowLineTextBrowser.h
+
diff --git a/src/ui/widgets/FlowLineTextBrowser.cpp b/src/ui/widgets/FlowLineTextBrowser.cpp new file mode 100644 index 0000000..43c6f86 --- /dev/null +++ b/src/ui/widgets/FlowLineTextBrowser.cpp @@ -0,0 +1,299 @@ +#include "FlowLineTextBrowser.h" + +#include +#include +#include +#include +#include +#include +#include + +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 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(); +} diff --git a/src/ui/widgets/FlowLineTextBrowser.h b/src/ui/widgets/FlowLineTextBrowser.h new file mode 100644 index 0000000..95887f6 --- /dev/null +++ b/src/ui/widgets/FlowLineTextBrowser.h @@ -0,0 +1,74 @@ +#ifndef FLOWLINETEXTBROWSER_H +#define FLOWLINETEXTBROWSER_H + +#include "JumpLineManager.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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