2017-09-28 21:53:59 +00:00
|
|
|
/* x64dbg RichTextPainter */
|
|
|
|
#include "RichTextPainter.h"
|
|
|
|
#include "CachedFontMetrics.h"
|
2018-08-15 08:45:15 +00:00
|
|
|
#include "utils/Configuration.h"
|
2017-09-28 21:53:59 +00:00
|
|
|
#include <QPainter>
|
2018-02-01 14:51:03 +00:00
|
|
|
#include <QTextBlock>
|
|
|
|
#include <QTextFragment>
|
2017-09-28 21:53:59 +00:00
|
|
|
|
|
|
|
//TODO: fix performance (possibly use QTextLayout?)
|
2018-03-21 20:32:32 +00:00
|
|
|
void RichTextPainter::paintRichText(QPainter *painter, int x, int y, int w, int h, int xinc,
|
|
|
|
const List &richText, CachedFontMetrics *fontMetrics)
|
2017-09-28 21:53:59 +00:00
|
|
|
{
|
|
|
|
QPen pen;
|
|
|
|
QPen highlightPen;
|
|
|
|
QBrush brush(Qt::cyan);
|
2018-03-21 20:32:32 +00:00
|
|
|
for (const CustomRichText_t &curRichText : richText) {
|
2017-09-28 21:53:59 +00:00
|
|
|
int textWidth = fontMetrics->width(curRichText.text);
|
|
|
|
int backgroundWidth = textWidth;
|
2018-03-21 20:32:32 +00:00
|
|
|
if (backgroundWidth + xinc > w)
|
2017-09-28 21:53:59 +00:00
|
|
|
backgroundWidth = w - xinc;
|
2018-03-21 20:32:32 +00:00
|
|
|
if (backgroundWidth <= 0) //stop drawing when going outside the specified width
|
2017-09-28 21:53:59 +00:00
|
|
|
break;
|
2018-03-21 20:32:32 +00:00
|
|
|
switch (curRichText.flags) {
|
2017-09-28 21:53:59 +00:00
|
|
|
case FlagNone: //defaults
|
2018-08-15 08:45:15 +00:00
|
|
|
pen.setColor(ConfigColor("btext").name());
|
|
|
|
painter->setPen(pen);
|
2017-09-28 21:53:59 +00:00
|
|
|
break;
|
|
|
|
case FlagColor: //color only
|
|
|
|
pen.setColor(curRichText.textColor);
|
|
|
|
painter->setPen(pen);
|
|
|
|
break;
|
|
|
|
case FlagBackground: //background only
|
2018-03-21 20:32:32 +00:00
|
|
|
if (backgroundWidth > 0 && curRichText.textBackground.alpha()) {
|
2017-09-28 21:53:59 +00:00
|
|
|
brush.setColor(curRichText.textBackground);
|
|
|
|
painter->fillRect(QRect(x + xinc, y, backgroundWidth, h), brush);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case FlagAll: //color+background
|
2018-03-21 20:32:32 +00:00
|
|
|
if (backgroundWidth > 0 && curRichText.textBackground.alpha()) {
|
2017-09-28 21:53:59 +00:00
|
|
|
brush.setColor(curRichText.textBackground);
|
|
|
|
painter->fillRect(QRect(x + xinc, y, backgroundWidth, h), brush);
|
|
|
|
}
|
|
|
|
pen.setColor(curRichText.textColor);
|
|
|
|
painter->setPen(pen);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
painter->drawText(QRect(x + xinc, y, w - xinc, h), Qt::TextBypassShaping, curRichText.text);
|
2018-03-21 20:32:32 +00:00
|
|
|
if (curRichText.highlight && curRichText.highlightColor.alpha()) {
|
2017-09-28 21:53:59 +00:00
|
|
|
highlightPen.setColor(curRichText.highlightColor);
|
|
|
|
highlightPen.setWidth(curRichText.highlightWidth);
|
|
|
|
painter->setPen(highlightPen);
|
|
|
|
int highlightOffsetX = curRichText.highlightConnectPrev ? -1 : 1;
|
2018-03-21 20:32:32 +00:00
|
|
|
painter->drawLine(x + xinc + highlightOffsetX, y + h - 1, x + xinc + backgroundWidth - 1,
|
|
|
|
y + h - 1);
|
2017-09-28 21:53:59 +00:00
|
|
|
}
|
|
|
|
xinc += textWidth;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief RichTextPainter::htmlRichText Convert rich text in x64dbg to HTML, for use by other applications
|
|
|
|
* @param richText The rich text to be converted to HTML format
|
|
|
|
* @param textHtml The HTML source. Any previous content will be preserved and new content will be appended at the end.
|
|
|
|
* @param textPlain The plain text. Any previous content will be preserved and new content will be appended at the end.
|
|
|
|
*/
|
2018-03-21 20:32:32 +00:00
|
|
|
void RichTextPainter::htmlRichText(const List &richText, QString &textHtml, QString &textPlain)
|
2017-09-28 21:53:59 +00:00
|
|
|
{
|
2018-03-21 20:32:32 +00:00
|
|
|
for (const CustomRichText_t &curRichText : richText) {
|
|
|
|
if (curRichText.text == " ") { //blank
|
2017-09-28 21:53:59 +00:00
|
|
|
textHtml += " ";
|
|
|
|
textPlain += " ";
|
|
|
|
continue;
|
|
|
|
}
|
2018-03-21 20:32:32 +00:00
|
|
|
switch (curRichText.flags) {
|
2017-09-28 21:53:59 +00:00
|
|
|
case FlagNone: //defaults
|
|
|
|
textHtml += "<span>";
|
|
|
|
break;
|
|
|
|
case FlagColor: //color only
|
|
|
|
textHtml += QString("<span style=\"color:%1\">").arg(curRichText.textColor.name());
|
|
|
|
break;
|
|
|
|
case FlagBackground: //background only
|
2018-03-21 20:32:32 +00:00
|
|
|
if (curRichText.textBackground !=
|
|
|
|
Qt::transparent) // QColor::name() returns "#000000" for transparent color. That's not desired. Leave it blank.
|
2017-09-28 21:53:59 +00:00
|
|
|
textHtml += QString("<span style=\"background-color:%1\">").arg(curRichText.textBackground.name());
|
|
|
|
else
|
|
|
|
textHtml += QString("<span>");
|
|
|
|
break;
|
|
|
|
case FlagAll: //color+background
|
2018-03-21 20:32:32 +00:00
|
|
|
if (curRichText.textBackground !=
|
|
|
|
Qt::transparent) // QColor::name() returns "#000000" for transparent color. That's not desired. Leave it blank.
|
|
|
|
textHtml += QString("<span style=\"color:%1; background-color:%2\">").arg(
|
|
|
|
curRichText.textColor.name(), curRichText.textBackground.name());
|
2017-09-28 21:53:59 +00:00
|
|
|
else
|
|
|
|
textHtml += QString("<span style=\"color:%1\">").arg(curRichText.textColor.name());
|
|
|
|
break;
|
|
|
|
}
|
2018-03-21 20:32:32 +00:00
|
|
|
if (curRichText.highlight) //Underline highlighted token
|
2017-09-28 21:53:59 +00:00
|
|
|
textHtml += "<u>";
|
|
|
|
textHtml += curRichText.text.toHtmlEscaped();
|
2018-03-21 20:32:32 +00:00
|
|
|
if (curRichText.highlight)
|
2017-09-28 21:53:59 +00:00
|
|
|
textHtml += "</u>";
|
|
|
|
textHtml += "</span>"; //Close the tag
|
|
|
|
textPlain += curRichText.text;
|
|
|
|
}
|
|
|
|
}
|
2017-12-19 16:00:42 +00:00
|
|
|
|
2018-02-01 14:51:03 +00:00
|
|
|
RichTextPainter::List RichTextPainter::fromTextDocument(const QTextDocument &doc)
|
|
|
|
{
|
|
|
|
List r;
|
|
|
|
|
2018-03-21 20:32:32 +00:00
|
|
|
for (QTextBlock block = doc.begin(); block != doc.end(); block = block.next()) {
|
|
|
|
for (QTextBlock::iterator it = block.begin(); it != block.end(); it++) {
|
2018-02-01 14:51:03 +00:00
|
|
|
QTextFragment fragment = it.fragment();
|
|
|
|
QTextCharFormat format = fragment.charFormat();
|
|
|
|
|
|
|
|
CustomRichText_t text;
|
|
|
|
text.text = fragment.text();
|
|
|
|
text.textColor = format.foreground().color();
|
|
|
|
text.textBackground = format.background().color();
|
|
|
|
|
|
|
|
bool hasForeground = format.hasProperty(QTextFormat::ForegroundBrush);
|
|
|
|
bool hasBackground = format.hasProperty(QTextFormat::BackgroundBrush);
|
|
|
|
|
2018-03-21 20:32:32 +00:00
|
|
|
if (hasForeground && !hasBackground) {
|
2018-02-01 14:51:03 +00:00
|
|
|
text.flags = FlagColor;
|
2018-03-21 20:32:32 +00:00
|
|
|
} else if (!hasForeground && hasBackground) {
|
2018-02-01 14:51:03 +00:00
|
|
|
text.flags = FlagBackground;
|
2018-03-21 20:32:32 +00:00
|
|
|
} else if (hasForeground && hasBackground) {
|
2018-02-01 14:51:03 +00:00
|
|
|
text.flags = FlagAll;
|
2018-03-21 20:32:32 +00:00
|
|
|
} else {
|
2018-02-01 14:51:03 +00:00
|
|
|
text.flags = FlagNone;
|
|
|
|
}
|
|
|
|
|
|
|
|
r.push_back(text);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
2018-03-21 20:32:32 +00:00
|
|
|
RichTextPainter::List RichTextPainter::cropped(const RichTextPainter::List &richText, int maxCols,
|
|
|
|
const QString &indicator, bool *croppedOut)
|
2017-12-19 16:00:42 +00:00
|
|
|
{
|
|
|
|
List r;
|
|
|
|
r.reserve(richText.size());
|
|
|
|
|
|
|
|
int cols = 0;
|
|
|
|
bool cropped = false;
|
2018-03-21 20:32:32 +00:00
|
|
|
for (const auto &text : richText) {
|
2017-12-19 16:00:42 +00:00
|
|
|
int textLength = text.text.size();
|
2018-03-21 20:32:32 +00:00
|
|
|
if (cols + textLength <= maxCols) {
|
2017-12-19 16:00:42 +00:00
|
|
|
r.push_back(text);
|
|
|
|
cols += textLength;
|
2018-03-21 20:32:32 +00:00
|
|
|
} else if (cols == maxCols) {
|
2017-12-19 16:00:42 +00:00
|
|
|
break;
|
2018-03-21 20:32:32 +00:00
|
|
|
} else {
|
2017-12-19 16:00:42 +00:00
|
|
|
CustomRichText_t croppedText = text;
|
|
|
|
croppedText.text.truncate(maxCols - cols);
|
|
|
|
r.push_back(croppedText);
|
|
|
|
cropped = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-21 20:32:32 +00:00
|
|
|
if (cropped && !indicator.isEmpty()) {
|
2017-12-19 16:00:42 +00:00
|
|
|
int indicatorCropLength = indicator.length();
|
2018-03-21 20:32:32 +00:00
|
|
|
if (indicatorCropLength > maxCols) {
|
2017-12-19 16:00:42 +00:00
|
|
|
indicatorCropLength = maxCols;
|
|
|
|
}
|
|
|
|
|
2018-03-21 20:32:32 +00:00
|
|
|
while (!r.empty()) {
|
2017-12-19 16:00:42 +00:00
|
|
|
auto &text = r.back();
|
|
|
|
|
2018-03-21 20:32:32 +00:00
|
|
|
if (text.text.length() >= indicatorCropLength) {
|
2017-12-19 16:00:42 +00:00
|
|
|
text.text.replace(text.text.length() - indicatorCropLength, indicatorCropLength, indicator);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
indicatorCropLength -= text.text.length();
|
|
|
|
r.pop_back();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-21 20:32:32 +00:00
|
|
|
if (croppedOut) {
|
2017-12-19 16:59:39 +00:00
|
|
|
*croppedOut = cropped;
|
|
|
|
}
|
2017-12-19 16:00:42 +00:00
|
|
|
return r;
|
|
|
|
}
|