Factor QPlainTextEdit block highlighting to separate class
MODIFY
src/colors.h
src/colors.h
#pragma once
#include <QColor>
+#include <functional>
+#include <memory>
namespace Ripes {
namespace Colors {
@@ -11,5 +13,20 @@ constexpr QColor CaliforniaGold = {0xFD, 0xB5, 0x15};
constexpr QColor Medalist = {0xC4, 0x82, 0x0E};
constexpr QColor FlatGreen = {0x4c, 0xde, 0x75};
+// Returns a function which returns a lighter color on each instantiation
+inline std::function<QColor()> incrementalColorGenerator(const QColor& start, unsigned steps) {
+ auto color = std::make_shared<QColor>(start);
+ return [color = color, steps] {
+ const int decRatio = 100 + 80 / steps;
+ QColor oldColor = *color;
+ *color = color->lighter(decRatio);
+ return oldColor;
+ };
+}
+
+inline std::function<QColor()> incrementalRedGenerator(unsigned steps) {
+ return incrementalColorGenerator(QColorConstants::Red.lighter(120), steps);
+}
+
}; // namespace Colors
} // namespace Ripes

ADD
src/editor/highlightabletextedit.cpp
+#include "highlightabletextedit.h"
+
+#include "STLExtras.h"
+
+#include <QPainter>
+#include <QTextBlock>
+
+#include "colors.h"
+
+namespace Ripes {
+
+HighlightableTextEdit::HighlightableTextEdit(QWidget* parent) : QPlainTextEdit(parent) {
+ connect(this, &QPlainTextEdit::textChanged, this, &HighlightableTextEdit::clearBlockHighlights);
+}
+
+void HighlightableTextEdit::paintEvent(QPaintEvent* event) {
+ QPlainTextEdit::paintEvent(event);
+
+ // Draw stage names for highlighted addresses
+ QPainter painter(viewport());
+
+ for (const auto& hb : m_highlightedBlocksText) {
+ if (!hb.first.isValid())
+ continue;
+ const QString stageString = hb.second.join('/');
+ const auto bbr = blockBoundingGeometry(hb.first);
+ painter.setFont(font());
+ const QRect stageStringRect = painter.fontMetrics().boundingRect(stageString);
+ QPointF drawAt = QPointF(bbr.width() - stageStringRect.width() - /* right-hand side padding*/ 10,
+ bbr.top() + (bbr.height() / 2.0 - stageStringRect.height() / 2.0));
+ painter.drawText(QRectF(drawAt.x(), drawAt.y(), stageStringRect.width(), stageStringRect.height()),
+ stageString);
+ }
+ painter.end();
+}
+
+void HighlightableTextEdit::applyHighlighting() {
+ QList<QTextEdit::ExtraSelection> selections;
+ llvm::transform(m_blockHighlights, std::back_inserter(selections), [](const auto& bh) { return bh.selection; });
+ setExtraSelections(selections);
+
+ // The block text is drawn on the viewport itself, which is not automatically redrawn when the extra selections
+ // change. So, to ensure that the new stage text is written, also update the viewport.
+ viewport()->update();
+}
+
+void HighlightableTextEdit::clearBlockHighlights() {
+ // A memory occurs within QPlainTextEdit::clear if extra selections has been set. This is most possibly a bug,
+ // but seems to be fixed if we manually clear the selections before we clear (and add new text) to the text
+ // edit.
+ setExtraSelections({});
+
+ m_highlightedBlocksText.clear();
+ m_highlightedBlocks.clear();
+ m_blockHighlights.clear();
+ viewport()->update();
+ update();
+}
+
+void HighlightableTextEdit::setBlockGradient(BlockHighlight& highlight) {
+ auto block = highlight.selection.cursor.block();
+ if (!block.isValid())
+ return;
+ const auto bbr = blockBoundingRect(block);
+ QLinearGradient grad(bbr.topLeft(), bbr.bottomRight());
+ grad.setColorAt(0, palette().base().color());
+ grad.setColorAt(1, highlight.color);
+ highlight.selection.format.setBackground(grad);
+}
+
+void HighlightableTextEdit::resizeEvent(QResizeEvent* e) {
+ QPlainTextEdit::resizeEvent(e);
+
+ // we need to update the highlighted lines whenever resizing the window to recalculate the highlighting gradient,
+ // reflecting the new widget size
+ for (auto& highlight : m_blockHighlights)
+ setBlockGradient(highlight);
+ applyHighlighting();
+}
+
+void HighlightableTextEdit::highlightBlock(const QTextBlock& block, const QColor& color, const QString& text) {
+ if (!block.isValid())
+ return;
+ if (!text.isEmpty())
+ m_highlightedBlocksText[block].push_back(text);
+
+ // Check if we're already highlighting the block. If this is the case, do not set an additional highlight on it.
+ if (m_highlightedBlocks.count(block))
+ return;
+
+ m_blockHighlights.push_back({});
+ auto& highlight = m_blockHighlights.back();
+ highlight.selection.cursor = QTextCursor(block);
+ highlight.selection.format.setProperty(QTextFormat::FullWidthSelection, true);
+ highlight.color = color;
+ setBlockGradient(highlight);
+ applyHighlighting();
+}
+
+} // namespace Ripes

ADD
src/editor/highlightabletextedit.h
+#pragma once
+
+#include <QFont>
+#include <QObject>
+#include <QPlainTextEdit>
+#include <QScrollBar>
+#include <QTimer>
+
+#include <set>
+
+namespace Ripes {
+
+// This implements a QPlainTextEdit with support for highlighting certain text blocks within the document.
+
+class HighlightableTextEdit : public QPlainTextEdit {
+ Q_OBJECT
+
+ struct BlockHighlight {
+ // Store the color which the block is highlighted with so that we can rehighlight a selection.
+ QColor color;
+ QTextEdit::ExtraSelection selection;
+ };
+
+public:
+ HighlightableTextEdit(QWidget* parent = nullptr);
+ void paintEvent(QPaintEvent* e) override;
+ /// Adds a highlight to the given block. An optional string may be provided, which will be painted at the right-hand
+ /// side of each block.
+ void highlightBlock(const QTextBlock& block, const QColor& color, const QString& text = QString());
+ /// Clears any currently active block highlightings.
+ void clearBlockHighlights();
+
+protected:
+ void resizeEvent(QResizeEvent* event) override;
+
+private:
+ void setBlockGradient(BlockHighlight& highlight);
+ void applyHighlighting();
+
+ /// A list of strings which will be printed at the right-hand side of each block
+ std::map<QTextBlock, QStringList> m_highlightedBlocksText;
+ /// A set containing the currently highlighted blocks
+ std::set<QTextBlock> m_highlightedBlocks;
+ /// A list containing the current highlightings being applied
+ QList<BlockHighlight> m_blockHighlights;
+};
+
+} // namespace Ripes

MODIFY
src/programviewer.cpp
src/programviewer.cpp
namespace Ripes {
-ProgramViewer::ProgramViewer(QWidget* parent) : QPlainTextEdit(parent) {
+ProgramViewer::ProgramViewer(QWidget* parent) : HighlightableTextEdit(parent) {
m_breakpointArea = new BreakpointArea(this);
connect(this, &QPlainTextEdit::blockCountChanged, this, &ProgramViewer::updateSidebarWidth);
@@ -51,12 +51,7 @@ void ProgramViewer::updateProgram(bool binary) {
const QString text = binary ? Assembler::binobjdump(ProcessorHandler::getProgram(), m_labelAddrOffsetMap)
: Assembler::objdump(ProcessorHandler::getProgram(), m_labelAddrOffsetMap);
- // A memory occurs within QPlainTextEdit::clear if extra selections has been set. This is most possibly a bug,
- // but seems to be fixed if we manually clear the selections before we clear (and add new text) to the text
- // edit.
- setExtraSelections({});
- setTextCursor(QTextCursor());
-
+ clearBlockHighlights();
setPlainText(text);
updateHighlightedAddresses();
}
@@ -100,12 +95,10 @@ void ProgramViewer::setFollowEnabled(bool enabled) {
}
void ProgramViewer::updateHighlightedAddresses() {
+ clearBlockHighlights();
const unsigned stages = ProcessorHandler::getProcessor()->stageCount();
- QColor bg = QColorConstants::Red.lighter(120);
- const int decRatio = 100 + 80 / stages;
+ auto colorGenerator = Colors::incrementalRedGenerator(stages);
QList<QTextEdit::ExtraSelection> highlights;
- std::set<AInt> highlightedPCs;
- m_highlightedBlocksText.clear();
for (unsigned sid = 0; sid < stages; sid++) {
const auto stageInfo = ProcessorHandler::getProcessor()->stageInfo(sid);
@@ -116,60 +109,15 @@ void ProgramViewer::updateHighlightedAddresses() {
// Record the stage name for the highlighted block for later painting
QString stageString = ProcessorHandler::getProcessor()->stageName(sid);
- if (!stageInfo.namedState.isEmpty()) {
+ if (!stageInfo.namedState.isEmpty())
stageString += " (" + stageInfo.namedState + ")";
+ highlightBlock(block, colorGenerator(), stageString);
}
- m_highlightedBlocksText[block] << stageString;
-
- // If a stage has already been highlighted (ie. an instruction exists in more than 1 stage at once), keep
- // the already set highlighting.
- if (highlightedPCs.count(stageInfo.pc)) {
- continue;
- }
-
- QTextEdit::ExtraSelection es;
- es.cursor = QTextCursor(block);
- es.format.setProperty(QTextFormat::FullWidthSelection, true);
-
- const auto bbr = blockBoundingRect(block);
- QLinearGradient grad(bbr.topLeft(), bbr.bottomRight());
- grad.setColorAt(0, palette().base().color());
- grad.setColorAt(1, bg);
- es.format.setBackground(grad);
-
- highlights << es;
- highlightedPCs.insert(stageInfo.pc);
- }
- bg = bg.lighter(decRatio);
}
- setExtraSelections(highlights);
if (m_following) {
updateCenterAddressFromProcessor();
}
-
- // The stage text is drawn on the viewport itself, which is not automatically redrawn when the extra selections
- // change. So, to ensure that the new stage text is written, also update the viewport.
- viewport()->update();
-}
-
-void ProgramViewer::paintEvent(QPaintEvent* event) {
- QPlainTextEdit::paintEvent(event);
-
- // Draw stage names for highlighted addresses
- QPainter painter(viewport());
-
- for (const auto& hb : m_highlightedBlocksText) {
- const QString stageString = hb.second.join('/');
- const auto bbr = blockBoundingGeometry(hb.first);
- painter.setFont(font());
- const QRect stageStringRect = painter.fontMetrics().boundingRect(stageString);
- QPointF drawAt = QPointF(bbr.width() - stageStringRect.width() - /* right-hand side padding*/ 10,
- bbr.top() + (bbr.height() / 2.0 - stageStringRect.height() / 2.0));
- painter.drawText(QRectF(drawAt.x(), drawAt.y(), stageStringRect.width(), stageStringRect.height()),
- stageString);
- }
- painter.end();
}
void ProgramViewer::breakpointAreaPaintEvent(QPaintEvent* event) {

MODIFY
src/programviewer.h
src/programviewer.h
#include "assembler/objdump.h"
#include "assembler/program.h"
+#include "highlightabletextedit.h"
#include "processorhandler.h"
namespace Ripes {
class BreakpointArea;
-class ProgramViewer : public QPlainTextEdit {
+class ProgramViewer : public HighlightableTextEdit {
Q_OBJECT
public:
ProgramViewer(QWidget* parent = nullptr);
- void paintEvent(QPaintEvent* e) override;
void breakpointAreaPaintEvent(QPaintEvent* event);
void breakpointClick(const QPoint& pos);
@@ -78,7 +78,6 @@ private:
* non-address lines encountered up to and including the given label.
*/
Assembler::AddrOffsetMap m_labelAddrOffsetMap;
- std::map<QTextBlock, QStringList> m_highlightedBlocksText;
};
class BreakpointArea : public QWidget {

Factor QPlainTextEdit block highlighting to separate class