Add simple VT-100 support

This commit is contained in:
Alex Spataru 2021-02-16 16:43:13 -05:00
parent e32c276303
commit 0f4128e721
3 changed files with 196 additions and 15 deletions

View File

@ -164,6 +164,7 @@ Control {
focus: true
readOnly: true
font.pixelSize: 12
vt100emulation: true
centerOnScroll: false
undoRedoEnabled: false
Layout.fillWidth: true
@ -299,6 +300,17 @@ Control {
}
}
CheckBox {
id: filterVt100
text: qsTr("VT-100")
Layout.alignment: Qt.AlignVCenter
checked: textEdit.vt100emulation
onCheckedChanged: {
if (textEdit.vt100emulation != checked)
textEdit.vt100emulation = checked
}
}
Item {
Layout.fillWidth: true
}

View File

@ -46,8 +46,11 @@ using namespace UI;
*/
QmlPlainTextEdit::QmlPlainTextEdit(QQuickItem *parent)
: QQuickPaintedItem(parent)
, m_autoscroll(true)
, m_emulateVt100(false)
, m_copyAvailable(false)
, m_textEdit(new QPlainTextEdit)
, m_terminalState(VT100_Text)
{
// Set item flags
setFlag(ItemHasContents, true);
@ -255,6 +258,15 @@ bool QmlPlainTextEdit::centerOnScroll() const
return textEdit()->centerOnScroll();
}
/**
* Returns true if the control shall parse basic VT-100 escape secuences. This can be
* useful if you need to interface with a shell/CLI from Serial Studio.
*/
bool QmlPlainTextEdit::vt100emulation() const
{
return m_emulateVt100;
}
/**
* This property holds whether undo and redo are enabled.
* Users are only able to undo or redo actions if this property is true, and if there is
@ -485,21 +497,7 @@ void QmlPlainTextEdit::setAutoscroll(const bool enabled)
*/
void QmlPlainTextEdit::insertText(const QString &text)
{
// Add text at the end of the text document
QTextCursor cursor(textEdit()->document());
cursor.beginEditBlock();
cursor.movePosition(QTextCursor::End);
cursor.insertText(text);
cursor.endEditBlock();
// Autoscroll to bottom (if needed)
updateScrollbarVisibility();
if (autoscroll())
scrollToBottom();
// Redraw the control
update();
emit textChanged();
addText(text, vt100emulation());
}
/**
@ -530,6 +528,17 @@ void QmlPlainTextEdit::setCenterOnScroll(const bool enabled)
emit centerOnScrollChanged();
}
/**
* Enables/disables interpretation of VT-100 escape secuences. This can be useful when
* interfacing through network ports or interfacing with a MCU that implements some
* kind of shell.
*/
void QmlPlainTextEdit::setVt100Emulation(const bool enabled)
{
m_emulateVt100 = enabled;
emit vt100EmulationChanged();
}
/**
* Enables/disables undo/redo history support.
*/
@ -638,6 +647,33 @@ void QmlPlainTextEdit::setCopyAvailable(const bool yes)
emit copyAvailableChanged();
}
/**
* Inserts the given @a text directly, no additional line breaks added.
*/
void QmlPlainTextEdit::addText(const QString &text, const bool enableVt100)
{
// Get text to insert
QString textToInsert = text;
if (enableVt100)
textToInsert = vt100Processing(text);
// Add text at the end of the text document
QTextCursor cursor(textEdit()->document());
cursor.beginEditBlock();
cursor.movePosition(QTextCursor::End);
cursor.insertText(textToInsert);
cursor.endEditBlock();
// Autoscroll to bottom (if needed)
updateScrollbarVisibility();
if (autoscroll())
scrollToBottom();
// Redraw the control
update();
emit textChanged();
}
/**
* Hack: call the appropiate protected mouse event handler function of the QPlainTextEdit
* item depending on event type
@ -720,3 +756,114 @@ void QmlPlainTextEdit::processWheelEvents(QWheelEvent *event)
}
}
}
/**
* Processes the given @a data to remove the escape sequences from the text. Colors and
* text format is not processed.
*
* Implementation based on https://github.com/sebcaux/QVTerminal
* List of commands: https://espterm.github.io/docs/VT100%20escape%20codes.html
*
* I did the necessary stuff to be able to watch ASCII Star Wars from Serial Studio.
* If you want/need to do more stuff, please make a PR.
*/
QString QmlPlainTextEdit::vt100Processing(const QString &data)
{
QString text;
QString command;
bool hasNumbers = false;
bool hasCommand = false;
for (int i = 0; i < data.length(); ++i)
{
QChar c = data.at(i);
switch (m_terminalState)
{
case VT100_Text:
if (c == 0x1B)
{
addText(text, false);
text.clear();
m_terminalState = VT100_Escape;
}
else if (c == "\n")
{
addText(text + "\n", false);
text.clear();
}
else
text.append(c);
break;
case VT100_Escape:
command.clear();
if (c == "[")
m_terminalState = VT100_Command;
else if (c == "(")
m_terminalState = VT100_ResetFont;
break;
case VT100_Command:
// Go to escape sequence
if (c == 0x1B)
{
m_terminalState = VT100_Escape;
break;
}
// Escape from command mode
hasNumbers = (c >= '0' && c <= '9');
hasCommand = (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
if (!hasNumbers && !hasCommand)
{
m_terminalState = VT100_Text;
break;
}
// Construct command
command.append(c);
// Clear screen
if (command == "2J")
{
textEdit()->clear();
m_terminalState = VT100_Text;
}
// Move cursor to upper left corner (ugly implementation)
else if (command == "H")
{
textEdit()->clear();
m_terminalState = VT100_Text;
}
// Clear line
else if (command == "2K")
{
textEdit()->setFocus();
auto storedCursor = textEdit()->textCursor();
textEdit()->moveCursor(QTextCursor::End, QTextCursor::MoveAnchor);
textEdit()->moveCursor(QTextCursor::StartOfLine,
QTextCursor::MoveAnchor);
textEdit()->moveCursor(QTextCursor::End, QTextCursor::KeepAnchor);
textEdit()->textCursor().removeSelectedText();
textEdit()->textCursor().deletePreviousChar();
textEdit()->setTextCursor(storedCursor);
m_terminalState = VT100_Text;
}
// Escape to normal text if command length >= 3 chars
else if (command.length() >= 3)
m_terminalState = VT100_Text;
break;
case VT100_ResetFont:
m_terminalState = VT100_Text;
break;
}
}
// Return VT-100 processed text
return text;
}

View File

@ -92,6 +92,10 @@ class QmlPlainTextEdit : public QQuickPaintedItem
READ scrollbarWidth
WRITE setScrollbarWidth
NOTIFY scrollbarWidthChanged)
Q_PROPERTY(bool vt100emulation
READ vt100emulation
WRITE setVt100Emulation
NOTIFY vt100EmulationChanged)
// clang-format on
signals:
@ -106,11 +110,21 @@ signals:
void widgetEnabledChanged();
void scrollbarWidthChanged();
void centerOnScrollChanged();
void vt100EmulationChanged();
void placeholderTextChanged();
void undoRedoEnabledChanged();
void maximumBlockCountChanged();
public:
enum VT100_State
{
VT100_Text,
VT100_Escape,
VT100_Command,
VT100_ResetFont
};
Q_ENUM(VT100_State)
QmlPlainTextEdit(QQuickItem *parent = 0);
~QmlPlainTextEdit();
@ -131,6 +145,7 @@ public:
bool copyAvailable() const;
bool widgetEnabled() const;
bool centerOnScroll() const;
bool vt100emulation() const;
bool undoRedoEnabled() const;
int maximumBlockCount() const;
QString placeholderText() const;
@ -155,6 +170,7 @@ public slots:
void setPalette(const QPalette &palette);
void setWidgetEnabled(const bool enabled);
void setCenterOnScroll(const bool enabled);
void setVt100Emulation(const bool enabled);
void setUndoRedoEnabled(const bool enabled);
void setPlaceholderText(const QString &text);
void scrollToBottom(const bool repaint = false);
@ -164,16 +180,22 @@ private slots:
void updateWidgetSize();
void updateScrollbarVisibility();
void setCopyAvailable(const bool yes);
void addText(const QString &text, const bool enableVt100);
protected:
void processMouseEvents(QMouseEvent *event);
void processWheelEvents(QWheelEvent *event);
private:
QString vt100Processing(const QString &data);
private:
QColor m_color;
bool m_autoscroll;
bool m_emulateVt100;
bool m_copyAvailable;
QPlainTextEdit *m_textEdit;
VT100_State m_terminalState;
};
}