Change to use QAnsiTextEdit for Console.

This commit is contained in:
Ernie Pasveer
2024-07-22 17:42:12 -05:00
parent c9979d2f26
commit 6385393524
11 changed files with 566 additions and 43 deletions
+2
View File
@@ -141,6 +141,7 @@ set(HEADER_FILES
QEditDelegate.h
QHContainerWidget.h
QImageViewer.h
QAnsiTextEdit.h
)
set(SOURCE_FILES
@@ -234,6 +235,7 @@ set(SOURCE_FILES
QEditDelegate.cpp
QHContainerWidget.cpp
QImageViewer.cpp
QAnsiTextEdit.cpp
)
if(${QTVERSION} STREQUAL "QT6")
+375
View File
@@ -0,0 +1,375 @@
#include "QAnsiTextEdit.h"
#include <QtCore/QDebug>
//
//
//
QAnsiTextEditFormattedText::QAnsiTextEditFormattedText(const QString& txt, const QTextCharFormat& fmt) {
text = txt;
format = fmt;
}
//
//
//
QList<QAnsiTextEditFormattedText> QAnsiTextEditEscapeCodeHandler::parseText (const QAnsiTextEditFormattedText& input) {
enum AnsiEscapeCodes {
ResetFormat = 0,
BoldText = 1,
UnderLinedText = 4,
TextColorStart = 30,
TextColorEnd = 37,
RgbTextColor = 38,
DefaultTextColor = 39,
BackgroundColorStart = 40,
BackgroundColorEnd = 47,
RgbBackgroundColor = 48,
DefaultBackgroundColor = 49
};
const QString escape = "\x1b[";
const QChar semicolon = ';';
const QChar colorTerminator = 'm';
const QChar eraseToEol = 'K';
QList<QAnsiTextEditFormattedText> outputData;
QTextCharFormat charFormat = _previousFormatClosed ? input.format : _previousFormat;
QString strippedText;
if (_pendingText.isEmpty()) {
strippedText = input.text;
}else{
strippedText = _pendingText.append(input.text);
_pendingText.clear();
}
while (!strippedText.isEmpty()) {
if (_waitingForTerminator) {
// We ignore all escape codes taking string arguments.
QString terminator = "\x1b\\";
int terminatorPos = strippedText.indexOf(terminator);
if (terminatorPos == -1 && !_alternateTerminator.isEmpty()) {
terminator = _alternateTerminator;
terminatorPos = strippedText.indexOf(terminator);
}
if (terminatorPos == -1) {
_pendingText = strippedText;
break;
}
_waitingForTerminator = false;
_alternateTerminator.clear();
strippedText.remove(0, terminatorPos + terminator.length());
if (strippedText.isEmpty()) {
break;
}
}
const int escapePos = strippedText.indexOf(escape.at(0));
if (escapePos < 0) {
outputData << QAnsiTextEditFormattedText(strippedText, charFormat);
break;
}else if (escapePos != 0) {
outputData << QAnsiTextEditFormattedText(strippedText.left(escapePos), charFormat);
strippedText.remove(0, escapePos);
}
while (!strippedText.isEmpty() && escape.at(0) == strippedText.at(0)) {
if (escape.startsWith(strippedText)) {
// control secquence is not complete
_pendingText += strippedText;
strippedText.clear();
break;
}
if (!strippedText.startsWith(escape)) {
switch (strippedText.at(1).toLatin1()) {
case '\\': // Unexpected terminator sequence.
Q_FALLTHROUGH();
case 'N': case 'O': // Ignore unsupported single-character sequences.
strippedText.remove(0, 2);
break;
case ']':
_alternateTerminator = QChar(7);
Q_FALLTHROUGH();
case 'P': case 'X': case '^': case '_':
strippedText.remove(0, 2);
_waitingForTerminator = true;
break;
default:
// not a control sequence
_pendingText.clear();
outputData << QAnsiTextEditFormattedText(strippedText.left(1), charFormat);
strippedText.remove(0, 1);
continue;
}
break;
}
_pendingText += strippedText.mid(0, escape.length());
strippedText.remove(0, escape.length());
// \e[K is not supported. Just strip it.
if (strippedText.startsWith(eraseToEol)) {
_pendingText.clear();
strippedText.remove(0, 1);
continue;
}
// get the number
QString strNumber;
QStringList numbers;
while (!strippedText.isEmpty()) {
if (strippedText.at(0).isDigit()) {
strNumber += strippedText.at(0);
}else{
if (!strNumber.isEmpty()) {
numbers << strNumber;
}
if (strNumber.isEmpty() || strippedText.at(0) != semicolon) {
break;
}
strNumber.clear();
}
_pendingText += strippedText.mid(0, 1);
strippedText.remove(0, 1);
}
if (strippedText.isEmpty()) {
break;
}
// remove terminating char
if (!strippedText.startsWith(colorTerminator)) {
_pendingText.clear();
strippedText.remove(0, 1);
break;
}
// got consistent control sequence, ok to clear pending text
_pendingText.clear();
strippedText.remove(0, 1);
if (numbers.isEmpty()) {
charFormat = input.format;
endFormatScope();
}
for (int i = 0; i < numbers.size(); ++i) {
const uint code = numbers.at(i).toUInt();
if (code >= TextColorStart && code <= TextColorEnd) {
//qDebug() << "TextColorStart/TextColorEnd called";
charFormat.setForeground(ansiColor(code - TextColorStart));
setFormatScope(charFormat);
}else if (code >= BackgroundColorStart && code <= BackgroundColorEnd) {
//qDebug() << "BackgroundColorStart/BackgroundColorEnd called";
charFormat.setBackground(ansiColor(code - BackgroundColorStart));
setFormatScope(charFormat);
}else{
switch (code) {
case ResetFormat:
//qDebug() << "ResetFormat called";
charFormat = QTextCharFormat();
setFormatScope(charFormat);
endFormatScope();
break;
case BoldText:
//qDebug() << "BoldText called";
charFormat.setFontWeight(QFont::ExtraBold);
setFormatScope(charFormat);
break;
case UnderLinedText:
//qDebug() << "UnderLinedText called";
charFormat.setFontUnderline(true);
setFormatScope(charFormat);
break;
case DefaultTextColor:
//qDebug() << "DefaultTextColor called";
charFormat.setForeground(input.format.foreground());
setFormatScope(charFormat);
break;
case DefaultBackgroundColor:
//qDebug() << "DefaultBackgroundColor called";
charFormat.setBackground(input.format.background());
setFormatScope(charFormat);
break;
case RgbTextColor:
case RgbBackgroundColor:
//qDebug() << "RgbTextColor/RgbBackgroundColor called";
// See http://en.wikipedia.org/wiki/ANSI_escape_code#Colors
if (++i >= numbers.size()) {
break;
}
switch (numbers.at(i).toInt()) {
case 2:
// RGB set with format: 38;2;<r>;<g>;<b>
if ((i + 3) < numbers.size()) {
(code == RgbTextColor) ?
charFormat.setForeground(QColor(numbers.at(i + 1).toInt(),
numbers.at(i + 2).toInt(),
numbers.at(i + 3).toInt())) :
charFormat.setBackground(QColor(numbers.at(i + 1).toInt(),
numbers.at(i + 2).toInt(),
numbers.at(i + 3).toInt()));
setFormatScope(charFormat);
}
i += 3;
break;
case 5:
// 256 color mode with format: 38;5;<i>
uint index = numbers.at(i + 1).toUInt();
QColor color;
if (index < 8) {
// The first 8 colors are standard low-intensity ANSI colors.
color = ansiColor(index);
}else if (index < 16) {
// The next 8 colors are standard high-intensity ANSI colors.
color = ansiColor(index - 8).lighter(150);
}else if (index < 232) {
// The next 216 colors are a 6x6x6 RGB cube.
uint o = index - 16;
color = QColor((o / 36) * 51, ((o / 6) % 6) * 51, (o % 6) * 51);
}else{
// The last 24 colors are a greyscale gradient.
int grey = int((index - 232) * 11);
color = QColor(grey, grey, grey);
}
if (code == RgbTextColor) {
charFormat.setForeground(color);
}else{
charFormat.setBackground(color);
}
setFormatScope(charFormat);
++i;
break;
}
break;
default:
qDebug() << "Unkown code:" << code;
break;
}
}
}
}
}
return outputData;
}
void QAnsiTextEditEscapeCodeHandler::endFormatScope () {
_previousFormatClosed = true;
}
void QAnsiTextEditEscapeCodeHandler::setFormatScope (const QTextCharFormat& charFormat) {
_previousFormat = charFormat;
_previousFormatClosed = false;
}
QTextCharFormat QAnsiTextEditEscapeCodeHandler::formatScope () const {
return _previousFormat;
}
QColor QAnsiTextEditEscapeCodeHandler::ansiColor(uint code) {
const int red = code & 1 ? 170 : 0;
const int green = code & 2 ? 170 : 0;
const int blue = code & 4 ? 170 : 0;
return QColor(red, green, blue);
}
//
//
//
QAnsiTextEdit::QAnsiTextEdit (QWidget* parent) : QPlainTextEdit(parent) {
}
QAnsiTextEdit::QAnsiTextEdit (const QString& text, QWidget* parent) : QPlainTextEdit(parent) {
setAnsiText(text);
}
QAnsiTextEdit::~QAnsiTextEdit () {
}
void QAnsiTextEdit::dumpCharFormat (QString string, QTextCharFormat format) {
qDebug() << "dumpCharFormat" << string << "Color" << format.foreground().color();
qDebug() << "dumpCharFormat" << string << "FontWeight" << format.fontWeight();
qDebug() << "dumpCharFormat" << string << "UnderLined" << format.fontUnderline();
qDebug() << "";
}
void QAnsiTextEdit::setAnsiText (const QString& text) {
// Reset the text edit
clear();
// Create the default text object;
QAnsiTextEditFormattedText ftext;
ftext.text = text;
ftext.format = currentCharFormat(); // Use cleared format.
// Parse it. A list of sub text objects is created.
QList<QAnsiTextEditFormattedText> ftexts = _escapeCodeHandler.parseText(ftext);
// Print each sub text object. Each one has its own text format.
for (const QAnsiTextEditFormattedText& ftext : ftexts) {
//dumpCharFormat(ftext.text, ftext.format);
QTextCursor cursor = textCursor();
cursor.setCharFormat(ftext.format);
cursor.insertText(ftext.text);
setTextCursor(cursor);
}
}
void QAnsiTextEdit::appendAnsiText (const QString& text) {
// Create the default text object;
QAnsiTextEditFormattedText ftext;
ftext.text = text;
// XXX ftext.format = currentCharFormat();
ftext.format = _escapeCodeHandler.formatScope();
// Parse it. A list of sub text objects is created.
QList<QAnsiTextEditFormattedText> ftexts = _escapeCodeHandler.parseText(ftext);
// Print each sub text object. Each one has its own text format.
for (const QAnsiTextEditFormattedText& ftext : ftexts) {
QTextCursor cursor = textCursor();
cursor.setCharFormat(ftext.format);
cursor.insertText(ftext.text);
setTextCursor(cursor);
}
}
void QAnsiTextEdit::insertAnsiText (const QString& text) {
// Create the default text object;
QAnsiTextEditFormattedText ftext;
ftext.text = text;
// XXX ftext.format = currentCharFormat();
ftext.format = _escapeCodeHandler.formatScope();
// Parse it. A list of sub text objects is created.
QList<QAnsiTextEditFormattedText> ftexts = _escapeCodeHandler.parseText(ftext);
// Print each sub text object. Each one has its own text format.
for (const QAnsiTextEditFormattedText& ftext : ftexts) {
QTextCursor cursor = textCursor();
cursor.setCharFormat(ftext.format);
cursor.insertText(ftext.text);
setTextCursor(cursor);
}
}
+57
View File
@@ -0,0 +1,57 @@
#pragma once
#include <QtWidgets/QPlainTextEdit>
#include <QtWidgets/QWidget>
#include <QtGui/QColor>
#include <QtGui/QTextCharFormat>
#include <QtCore/QString>
#include <QtCore/QList>
class QAnsiTextEditFormattedText {
public:
QAnsiTextEditFormattedText() = default;
QAnsiTextEditFormattedText(const QString& txt, const QTextCharFormat& fmt = QTextCharFormat());
QString text;
QTextCharFormat format;
};
class QAnsiTextEditEscapeCodeHandler {
public:
QList<QAnsiTextEditFormattedText> parseText (const QAnsiTextEditFormattedText& input);
void endFormatScope ();
void setFormatScope (const QTextCharFormat& charFormat);
QTextCharFormat formatScope () const;
private:
QColor ansiColor (uint code);
bool _previousFormatClosed = true;
bool _waitingForTerminator = false;
QString _alternateTerminator;
QTextCharFormat _previousFormat;
QString _pendingText;
};
class QAnsiTextEdit : public QPlainTextEdit {
Q_OBJECT
public:
explicit QAnsiTextEdit(QWidget* parent = 0);
explicit QAnsiTextEdit(const QString& text, QWidget* parent = 0);
~QAnsiTextEdit();
private:
void dumpCharFormat (QString string, QTextCharFormat format);
public slots:
void setAnsiText (const QString& text);
void appendAnsiText (const QString& text);
void insertAnsiText (const QString& text);
private:
QAnsiTextEditEscapeCodeHandler _escapeCodeHandler;
};
+8 -40
View File
@@ -45,8 +45,6 @@ SeerConsoleWidget::SeerConsoleWidget (QWidget* parent) : QWidget(parent) {
textEdit->setLineWrapMode(QPlainTextEdit::NoWrap); // No wrap
wrapTextCheckBox->setCheckState(Qt::Unchecked); // No wrap
_cursor = QTextCursor(textEdit->document());
// Create psuedo terminal for console.
createConsole();
connectConsole();
@@ -76,44 +74,15 @@ const QString& SeerConsoleWidget::ttyDeviceName () const {
void SeerConsoleWidget::handleText (const char* buffer, int count) {
// parse off lines
const char* start = buffer;
while (count > 0) {
int len = 0;
while (count > 0 && start[len] != '\n' && start[len] != '\r') {
--count;
++len;
}
if (len > 0) {
QString str = QString::fromLatin1(start, len);
// replace text in the last line
// this selection is non-empty only after a '\r' that was not
// followed by a '\n'
_cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, len);
_cursor.insertText(str);
start += len;
len = 0;
}
if (count > 0 && *start == '\r') {
++start;
--count;
_cursor.movePosition(QTextCursor::StartOfLine);
}
if (count > 0 && *start == '\n') {
++start;
--count;
_cursor.movePosition(QTextCursor::End);
_cursor.insertText(QString('\n'));
}
textEdit->verticalScrollBar()->setValue(textEdit->verticalScrollBar()->maximum());
if (count < 1) {
return;
}
QString str = QString::fromLatin1(buffer, count);
textEdit->insertAnsiText(str);
return;
}
void SeerConsoleWidget::handleChangeWindowTitle (QString title) {
@@ -127,7 +96,6 @@ void SeerConsoleWidget::handleChangeWindowTitle (QString title) {
void SeerConsoleWidget::handleClearButton () {
textEdit->clear();
_cursor.movePosition(QTextCursor::End);
}
void SeerConsoleWidget::handlePrintButton () {
-1
View File
@@ -47,7 +47,6 @@ class SeerConsoleWidget : public QWidget, protected Ui::SeerConsoleWidgetForm {
private:
QString _mode;
QTextCursor _cursor;
QString _ttyDeviceName;
int _ptsFD;
QSocketNotifier* _ptsListener;
+6 -1
View File
@@ -62,7 +62,7 @@
</widget>
</item>
<item row="0" column="0" colspan="6">
<widget class="QPlainTextEdit" name="textEdit">
<widget class="QAnsiTextEdit" name="textEdit">
<property name="lineWrapMode">
<enum>QPlainTextEdit::NoWrap</enum>
</property>
@@ -129,6 +129,11 @@
<extends>QLineEdit</extends>
<header location="global">QHistoryLineEdit.h</header>
</customwidget>
<customwidget>
<class>QAnsiTextEdit</class>
<extends>QPlainTextEdit</extends>
<header location="global">QAnsiTextEdit.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="resource.qrc"/>
+1 -1
View File
@@ -110,7 +110,7 @@ void SeerCppSourceHighlighter::setHighlighterSettings (const SeerHighlighterSett
void SeerCppSourceHighlighter::highlightBlock (const QString& text) {
for (const HighlightingRule& rule : qAsConst(_highlightingRules)) {
for (const HighlightingRule& rule : std::as_const(_highlightingRules)) {
QRegularExpressionMatchIterator matchIterator = rule.pattern.globalMatch(text);
+1
View File
@@ -0,0 +1 @@
helloansicodes
+80
View File
@@ -0,0 +1,80 @@
/*
* This is free and unencumbered software released into the public domain.
*
* For more information, please refer to <https://unlicense.org>
*/
//Regular text
#define BLK "\033[0;30m"
#define RED "\033[0;31m"
#define GRN "\033[0;32m"
#define YEL "\033[0;33m"
#define BLU "\033[0;34m"
#define MAG "\033[0;35m"
#define CYN "\033[0;36m"
#define WHT "\033[0;37m"
//Regular bold text
#define BBLK "\033[1;30m"
#define BRED "\033[1;31m"
#define BGRN "\033[1;32m"
#define BYEL "\033[1;33m"
#define BBLU "\033[1;34m"
#define BMAG "\033[1;35m"
#define BCYN "\033[1;36m"
#define BWHT "\033[1;37m"
//Regular underline text
#define UBLK "\033[4;30m"
#define URED "\033[4;31m"
#define UGRN "\033[4;32m"
#define UYEL "\033[4;33m"
#define UBLU "\033[4;34m"
#define UMAG "\033[4;35m"
#define UCYN "\033[4;36m"
#define UWHT "\033[4;37m"
//Regular background
#define BLKB "\033[40m"
#define REDB "\033[41m"
#define GRNB "\033[42m"
#define YELB "\033[43m"
#define BLUB "\033[44m"
#define MAGB "\033[45m"
#define CYNB "\033[46m"
#define WHTB "\033[47m"
//High intensty background
#define BLKHB "\033[0;100m"
#define REDHB "\033[0;101m"
#define GRNHB "\033[0;102m"
#define YELHB "\033[0;103m"
#define BLUHB "\033[0;104m"
#define MAGHB "\033[0;105m"
#define CYNHB "\033[0;106m"
#define WHTHB "\033[0;107m"
//High intensty text
#define HBLK "\033[0;90m"
#define HRED "\033[0;91m"
#define HGRN "\033[0;92m"
#define HYEL "\033[0;93m"
#define HBLU "\033[0;94m"
#define HMAG "\033[0;95m"
#define HCYN "\033[0;96m"
#define HWHT "\033[0;97m"
//Bold high intensity text
#define BHBLK "\033[1;90m"
#define BHRED "\033[1;91m"
#define BHGRN "\033[1;92m"
#define BHYEL "\033[1;93m"
#define BHBLU "\033[1;94m"
#define BHMAG "\033[1;95m"
#define BHCYN "\033[1;96m"
#define BHWHT "\033[1;97m"
//Reset
//#define reset "\033[0m"
#define CRESET "\033[0m"
#define COLOR_RESET "\033[0m"
+10
View File
@@ -0,0 +1,10 @@
.PHONY: all
all: helloansicodes
helloansicodes: helloansicodes.c ANSI-color-codes.h
g++ -g -o helloansicodes helloansicodes.c
.PHONY: clean
clean:
rm -f helloansicodes helloansicodes.o
+26
View File
@@ -0,0 +1,26 @@
#include "ANSI-color-codes.h"
#include <stdio.h>
int main() {
printf("STARTING ANSI CODE TESTS.\n\n");
printf(BRED "Hey this is the color red, and it's bold! \n" CRESET);
printf(RED "If " BLU "you " YEL "are " GRN "bored " CYN "do " MAG "this! \n" CRESET);
printf(BRED "If " BBLU "you " BYEL "are " BGRN "bored " BCYN "do " BMAG "this! \n" CRESET);
printf(URED "If " UBLU "you " UYEL "are " UGRN "bored " UCYN "do " UMAG "this! \n" CRESET);
printf("\n");
printf(" \033[4;31mLight Colors\033[0m \t\t\t \033[1;4;31mDark Colors\033[0m\n");
printf(" \033[0;30;47m Black \033[0m 0;30m \t\t \033[1;30;40m Dark Gray \033[0m 1;30m\n");
printf(" \033[0;31;47m Red \033[0m 0;31m \t\t \033[1;31;40m Dark Red \033[0m 1;31m\n");
printf(" \033[0;32;47m Green \033[0m 0;32m \t\t \033[1;32;40m Dark Green \033[0m 1;32m\n");
printf(" \033[0;33;47m Brown \033[0m 0;33m \t\t \033[1;33;40m Yellow \033[0m 1;33m\n");
printf(" \033[0;34;47m Blue \033[0m 0;34m \t\t \033[1;34;40m Dark Blue \033[0m 1;34m\n");
printf(" \033[0;35;47m Magenta \033[0m 0;35m \t\t \033[1;35;40m Dark Magenta\033[0m 1;35m\n");
printf(" \033[0;36;47m Cyan \033[0m 0;36m \t\t \033[1;36;40m Dark Cyan \033[0m 1;36m\n");
printf(" \033[0;37;47m Light Gray\033[0m 0;37m \t\t \033[1;37;40m White \033[0m 1;37m\n");
return 0;
}