mirror of
https://github.com/obhq/obliteration.git
synced 2024-11-27 05:00:24 +00:00
Initializes ANSI escape code supports (#131)
This commit is contained in:
parent
b612ca53ec
commit
7abe3b4187
@ -140,8 +140,7 @@ If you don't have a PS4 application for testing you can download PS Scene Quiz f
|
||||
|
||||
### Rules for Rust sources
|
||||
|
||||
- Don't be afraid to use `unsafe` when it is necessary. We are writing an application that requires very high-performance code and we use Rust to assist us on this task, not to use Rust to compromise performance that C/C++ can provide.
|
||||
- Any functions that operate on pointers don't need to mark as `unsafe`. The reason is that it will require the caller to wrap it in `unsafe`. We already embrace `unsafe` code so no point to make it harder to use.
|
||||
- Use unsafe code only when you know what you are doing. When you do try to wrap it in a safe function so other people who are not familiar with unsafe code can have a safe life.
|
||||
- Don't chain method calls without an intermediate variable if the result code is hard to follow. We encourage code readability as a pleasure when writing so try to make it easy to read and understand for other people.
|
||||
- Do not blindly cast an integer. Make sure the value can fit in a destination type. We don't have any plans to support non-64-bit systems so the pointer size and its related types like `usize` are always 64-bits.
|
||||
|
||||
@ -166,6 +165,7 @@ We use icons from https://materialdesignicons.com for action icons (e.g. on the
|
||||
|
||||
## License
|
||||
|
||||
- All source code except `src/pfs` and `src/pkg` is licensed under MIT license.
|
||||
- `src/ansi_escape.hpp`, `src/ansi_escape.cpp`, `src/log_formatter.hpp` and `src/log_formatter.cpp` are licensed under GPL-3.0 only.
|
||||
- `src/pfs` and `src/pkg` are licensed under LGPL-3.0 license.
|
||||
- All other source code is licensed under MIT license.
|
||||
- All release binaries are under GPL-3.0 license.
|
||||
|
@ -28,11 +28,13 @@ ExternalProject_Add(core
|
||||
|
||||
# Setup application target.
|
||||
add_executable(obliteration WIN32
|
||||
ansi_escape.cpp
|
||||
app_data.cpp
|
||||
game_models.cpp
|
||||
game_settings.cpp
|
||||
game_settings_dialog.cpp
|
||||
initialize_dialog.cpp
|
||||
log_formatter.cpp
|
||||
main.cpp
|
||||
main_window.cpp
|
||||
path.cpp
|
||||
|
257
src/ansi_escape.cpp
Normal file
257
src/ansi_escape.cpp
Normal file
@ -0,0 +1,257 @@
|
||||
// This file is a derived works from Qt Creator.
|
||||
// Licensed under GPL-3.0-only.
|
||||
#include "ansi_escape.hpp"
|
||||
|
||||
static QColor ansiColor(uint code)
|
||||
{
|
||||
// QTC_ASSERT(code < 8, return QColor());
|
||||
if (!(code < 8)) {
|
||||
return QColor();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
QList<FormattedText> AnsiEscape::parseText(const FormattedText &input)
|
||||
{
|
||||
enum AnsiEscapeCodes {
|
||||
ResetFormat = 0,
|
||||
BoldText = 1,
|
||||
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<FormattedText> outputData;
|
||||
QTextCharFormat charFormat = m_previousFormatClosed ? input.format : m_previousFormat;
|
||||
QString strippedText;
|
||||
if (m_pendingText.isEmpty()) {
|
||||
strippedText = input.text;
|
||||
} else {
|
||||
strippedText = m_pendingText.append(input.text);
|
||||
m_pendingText.clear();
|
||||
}
|
||||
|
||||
while (!strippedText.isEmpty()) {
|
||||
// QTC_ASSERT(m_pendingText.isEmpty(), break);
|
||||
if (!m_pendingText.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (m_waitingForTerminator) {
|
||||
// We ignore all escape codes taking string arguments.
|
||||
QString terminator = "\x1b\\";
|
||||
int terminatorPos = strippedText.indexOf(terminator);
|
||||
if (terminatorPos == -1 && !m_alternateTerminator.isEmpty()) {
|
||||
terminator = m_alternateTerminator;
|
||||
terminatorPos = strippedText.indexOf(terminator);
|
||||
}
|
||||
if (terminatorPos == -1) {
|
||||
m_pendingText = strippedText;
|
||||
break;
|
||||
}
|
||||
m_waitingForTerminator = false;
|
||||
m_alternateTerminator.clear();
|
||||
strippedText.remove(0, terminatorPos + terminator.length());
|
||||
if (strippedText.isEmpty())
|
||||
break;
|
||||
}
|
||||
const int escapePos = strippedText.indexOf(escape.at(0));
|
||||
if (escapePos < 0) {
|
||||
outputData << FormattedText(strippedText, charFormat);
|
||||
break;
|
||||
} else if (escapePos != 0) {
|
||||
outputData << FormattedText(strippedText.left(escapePos), charFormat);
|
||||
strippedText.remove(0, escapePos);
|
||||
}
|
||||
|
||||
// QTC_ASSERT(strippedText.at(0) == escape.at(0), break);
|
||||
if (!(strippedText.at(0) == escape.at(0))) {
|
||||
break;
|
||||
}
|
||||
|
||||
while (!strippedText.isEmpty() && escape.at(0) == strippedText.at(0)) {
|
||||
if (escape.startsWith(strippedText)) {
|
||||
// control secquence is not complete
|
||||
m_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 ']':
|
||||
m_alternateTerminator = QChar(7);
|
||||
Q_FALLTHROUGH();
|
||||
case 'P': case 'X': case '^': case '_':
|
||||
strippedText.remove(0, 2);
|
||||
m_waitingForTerminator = true;
|
||||
break;
|
||||
default:
|
||||
// not a control sequence
|
||||
m_pendingText.clear();
|
||||
outputData << FormattedText(strippedText.left(1), charFormat);
|
||||
strippedText.remove(0, 1);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
m_pendingText += strippedText.mid(0, escape.length());
|
||||
strippedText.remove(0, escape.length());
|
||||
|
||||
// \e[K is not supported. Just strip it.
|
||||
if (strippedText.startsWith(eraseToEol)) {
|
||||
m_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();
|
||||
}
|
||||
m_pendingText += strippedText.mid(0, 1);
|
||||
strippedText.remove(0, 1);
|
||||
}
|
||||
if (strippedText.isEmpty())
|
||||
break;
|
||||
|
||||
// remove terminating char
|
||||
if (!strippedText.startsWith(colorTerminator)) {
|
||||
m_pendingText.clear();
|
||||
strippedText.remove(0, 1);
|
||||
break;
|
||||
}
|
||||
// got consistent control sequence, ok to clear pending text
|
||||
m_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) {
|
||||
charFormat.setForeground(ansiColor(code - TextColorStart));
|
||||
setFormatScope(charFormat);
|
||||
} else if (code >= BackgroundColorStart && code <= BackgroundColorEnd) {
|
||||
charFormat.setBackground(ansiColor(code - BackgroundColorStart));
|
||||
setFormatScope(charFormat);
|
||||
} else {
|
||||
switch (code) {
|
||||
case ResetFormat:
|
||||
charFormat = input.format;
|
||||
endFormatScope();
|
||||
break;
|
||||
case BoldText:
|
||||
charFormat.setFontWeight(QFont::Bold);
|
||||
setFormatScope(charFormat);
|
||||
break;
|
||||
case DefaultTextColor:
|
||||
charFormat.setForeground(input.format.foreground());
|
||||
setFormatScope(charFormat);
|
||||
break;
|
||||
case DefaultBackgroundColor:
|
||||
charFormat.setBackground(input.format.background());
|
||||
setFormatScope(charFormat);
|
||||
break;
|
||||
case RgbTextColor:
|
||||
case RgbBackgroundColor:
|
||||
// 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:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return outputData;
|
||||
}
|
||||
|
||||
void AnsiEscape::endFormatScope()
|
||||
{
|
||||
m_previousFormatClosed = true;
|
||||
}
|
||||
|
||||
void AnsiEscape::setFormatScope(const QTextCharFormat &charFormat)
|
||||
{
|
||||
m_previousFormat = charFormat;
|
||||
m_previousFormatClosed = false;
|
||||
}
|
36
src/ansi_escape.hpp
Normal file
36
src/ansi_escape.hpp
Normal file
@ -0,0 +1,36 @@
|
||||
// This file is a derived works from Qt Creator.
|
||||
// Licensed under GPL-3.0-only.
|
||||
#pragma once
|
||||
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
#include <QTextCharFormat>
|
||||
|
||||
class FormattedText {
|
||||
public:
|
||||
FormattedText() = default;
|
||||
FormattedText(const QString &txt, const QTextCharFormat &fmt = QTextCharFormat()) :
|
||||
text(txt),
|
||||
format(fmt)
|
||||
{
|
||||
}
|
||||
|
||||
QString text;
|
||||
QTextCharFormat format;
|
||||
};
|
||||
|
||||
class AnsiEscape {
|
||||
public:
|
||||
QList<FormattedText> parseText(const FormattedText &input);
|
||||
void endFormatScope();
|
||||
|
||||
private:
|
||||
void setFormatScope(const QTextCharFormat &charFormat);
|
||||
|
||||
private:
|
||||
bool m_previousFormatClosed = true;
|
||||
bool m_waitingForTerminator = false;
|
||||
QString m_alternateTerminator;
|
||||
QTextCharFormat m_previousFormat;
|
||||
QString m_pendingText;
|
||||
};
|
203
src/log_formatter.cpp
Normal file
203
src/log_formatter.cpp
Normal file
@ -0,0 +1,203 @@
|
||||
// This file is a derived works from Qt Creator.
|
||||
// Licensed under GPL-3.0-only.
|
||||
#include "log_formatter.hpp"
|
||||
|
||||
#include <QBrush>
|
||||
#include <QPlainTextEdit>
|
||||
#include <QScrollBar>
|
||||
|
||||
static QString normalizeNewlines(const QString &text)
|
||||
{
|
||||
QString res = text;
|
||||
const auto newEnd = std::unique(res.begin(), res.end(), [](const QChar c1, const QChar c2) {
|
||||
return c1 == '\r' && c2 == '\r'; // QTCREATORBUG-24556
|
||||
});
|
||||
res.chop(std::distance(newEnd, res.end()));
|
||||
res.replace("\r\n", "\n");
|
||||
return res;
|
||||
}
|
||||
|
||||
LogFormatter::LogFormatter(QPlainTextEdit *output, QObject *parent) :
|
||||
QObject(parent),
|
||||
m_output(output),
|
||||
m_cursor(output->textCursor()),
|
||||
m_prependLineFeed(false),
|
||||
m_prependCarriageReturn(false)
|
||||
{
|
||||
m_cursor.movePosition(QTextCursor::End);
|
||||
initFormats();
|
||||
}
|
||||
|
||||
LogFormatter::~LogFormatter()
|
||||
{
|
||||
}
|
||||
|
||||
void LogFormatter::appendMessage(const QString &text, LogFormat format)
|
||||
{
|
||||
if (text.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we have an existing incomplete line and its format is different from this one,
|
||||
// then we consider the two messages unrelated. We re-insert the previous incomplete line,
|
||||
// possibly formatted now, and start from scratch with the new input.
|
||||
if (!m_incompleteLine.first.isEmpty() && m_incompleteLine.second != format) {
|
||||
flushIncompleteLine();
|
||||
}
|
||||
|
||||
QString out = text;
|
||||
if (m_prependCarriageReturn) {
|
||||
m_prependCarriageReturn = false;
|
||||
out.prepend('\r');
|
||||
}
|
||||
out = normalizeNewlines(out);
|
||||
if (out.endsWith('\r')) {
|
||||
m_prependCarriageReturn = true;
|
||||
out.chop(1);
|
||||
}
|
||||
|
||||
// If the input is a single incomplete line, we do not forward it to the specialized
|
||||
// formatting code, but simply dump it as-is. Once it becomes complete or it needs to
|
||||
// be flushed for other reasons, we remove the unformatted part and re-insert it, this
|
||||
// time with proper formatting.
|
||||
if (!out.contains('\n')) {
|
||||
dumpIncompleteLine(out, format);
|
||||
return;
|
||||
}
|
||||
|
||||
// We have at least one complete line, so let's remove the previously dumped
|
||||
// incomplete line and prepend it to the first line of our new input.
|
||||
if (!m_incompleteLine.first.isEmpty()) {
|
||||
clearLastLine();
|
||||
out.prepend(m_incompleteLine.first);
|
||||
m_incompleteLine.first.clear();
|
||||
}
|
||||
|
||||
// Forward all complete lines to the specialized formatting code, and handle a
|
||||
// potential trailing incomplete line the same way as above.
|
||||
for (int startPos = 0; ;) {
|
||||
const int eolPos = out.indexOf('\n', startPos);
|
||||
if (eolPos == -1) {
|
||||
dumpIncompleteLine(out.mid(startPos), format);
|
||||
break;
|
||||
}
|
||||
doAppendMessage(out.mid(startPos, eolPos - startPos), format);
|
||||
scroll();
|
||||
m_prependLineFeed = true;
|
||||
startPos = eolPos + 1;
|
||||
}
|
||||
}
|
||||
|
||||
void LogFormatter::reset()
|
||||
{
|
||||
m_output->clear();
|
||||
m_prependLineFeed = false;
|
||||
m_prependCarriageReturn = false;
|
||||
m_incompleteLine.first.clear();
|
||||
m_escapeCodeHandler = AnsiEscape();
|
||||
}
|
||||
|
||||
void LogFormatter::initFormats()
|
||||
{
|
||||
m_formats[InfoMessageFormat].setForeground(QBrush(Qt::darkGreen));
|
||||
m_formats[ErrorMessageFormat].setForeground(QBrush(Qt::darkRed));
|
||||
m_formats[ErrorMessageFormat].setFontWeight(QFont::Bold);
|
||||
m_formats[WarnMessageFormat].setForeground(QBrush(Qt::darkYellow));
|
||||
m_formats[WarnMessageFormat].setFontWeight(QFont::Bold);
|
||||
}
|
||||
|
||||
void LogFormatter::doAppendMessage(const QString &text, LogFormat format)
|
||||
{
|
||||
QTextCharFormat charFmt = charFormat(format);
|
||||
QList<FormattedText> formattedText = parseAnsi(text, charFmt);
|
||||
|
||||
const QString cleanLine = std::accumulate(formattedText.begin(), formattedText.end(), QString(),
|
||||
[](const FormattedText &t1, const FormattedText &t2) -> QString
|
||||
{ return t1.text + t2.text; });
|
||||
|
||||
for (FormattedText output : formattedText) {
|
||||
append(output.text, output.format);
|
||||
}
|
||||
|
||||
if (formattedText.isEmpty()) {
|
||||
append({}, charFmt); // This might cause insertion of a newline character.
|
||||
}
|
||||
}
|
||||
|
||||
void LogFormatter::append(const QString &text, const QTextCharFormat &format)
|
||||
{
|
||||
flushTrailingNewline();
|
||||
|
||||
int startPos = 0;
|
||||
int crPos = -1;
|
||||
|
||||
while ((crPos = text.indexOf('\r', startPos)) >= 0) {
|
||||
m_cursor.insertText(text.mid(startPos, crPos - startPos), format);
|
||||
m_cursor.clearSelection();
|
||||
m_cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor);
|
||||
startPos = crPos + 1;
|
||||
}
|
||||
|
||||
if (startPos < text.size()) {
|
||||
m_cursor.insertText(text.mid(startPos), format);
|
||||
}
|
||||
}
|
||||
|
||||
void LogFormatter::flushTrailingNewline()
|
||||
{
|
||||
if (m_prependLineFeed) {
|
||||
m_cursor.insertText("\n");
|
||||
m_prependLineFeed = false;
|
||||
}
|
||||
}
|
||||
|
||||
QTextCharFormat LogFormatter::charFormat(LogFormat format) const
|
||||
{
|
||||
return m_formats[format];
|
||||
}
|
||||
|
||||
QList<FormattedText> LogFormatter::parseAnsi(const QString &text, const QTextCharFormat &format)
|
||||
{
|
||||
return m_escapeCodeHandler.parseText(FormattedText(text, format));
|
||||
}
|
||||
|
||||
void LogFormatter::dumpIncompleteLine(const QString &line, LogFormat format)
|
||||
{
|
||||
if (line.isEmpty())
|
||||
return;
|
||||
|
||||
append(line, charFormat(format));
|
||||
m_incompleteLine.first.append(line);
|
||||
m_incompleteLine.second = format;
|
||||
}
|
||||
|
||||
void LogFormatter::flushIncompleteLine()
|
||||
{
|
||||
clearLastLine();
|
||||
doAppendMessage(m_incompleteLine.first, m_incompleteLine.second);
|
||||
m_incompleteLine.first.clear();
|
||||
}
|
||||
|
||||
void LogFormatter::clearLastLine()
|
||||
{
|
||||
// Note that this approach will fail if the text edit is not read-only and users
|
||||
// have messed with the last line between programmatic inputs.
|
||||
// We live with this risk, as all the alternatives are worse.
|
||||
if (!m_cursor.atEnd()) {
|
||||
m_cursor.movePosition(QTextCursor::End);
|
||||
}
|
||||
|
||||
m_cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor);
|
||||
m_cursor.removeSelectedText();
|
||||
}
|
||||
|
||||
void LogFormatter::scroll()
|
||||
{
|
||||
auto bar = m_output->verticalScrollBar();
|
||||
auto max = bar->maximum();
|
||||
auto bottom = (bar->value() >= (max - 4)); // 4 is an error threshold.
|
||||
|
||||
if (bottom) {
|
||||
bar->setValue(max);
|
||||
}
|
||||
}
|
51
src/log_formatter.hpp
Normal file
51
src/log_formatter.hpp
Normal file
@ -0,0 +1,51 @@
|
||||
// This file is a derived works from Qt Creator.
|
||||
// Licensed under GPL-3.0-only.
|
||||
#pragma once
|
||||
|
||||
#include "ansi_escape.hpp"
|
||||
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
#include <QTextCharFormat>
|
||||
#include <QTextCursor>
|
||||
|
||||
class QPlainTextEdit;
|
||||
|
||||
enum LogFormat {
|
||||
InfoMessageFormat,
|
||||
ErrorMessageFormat,
|
||||
WarnMessageFormat,
|
||||
NumberOfFormats // Keep this entry last.
|
||||
};
|
||||
|
||||
class LogFormatter : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
LogFormatter(QPlainTextEdit *output, QObject *parent = nullptr);
|
||||
~LogFormatter() override;
|
||||
|
||||
public:
|
||||
void appendMessage(const QString &text, LogFormat format);
|
||||
void reset();
|
||||
|
||||
private:
|
||||
void initFormats();
|
||||
void doAppendMessage(const QString &text, LogFormat format);
|
||||
void append(const QString &text, const QTextCharFormat &format);
|
||||
void flushTrailingNewline();
|
||||
QTextCharFormat charFormat(LogFormat format) const;
|
||||
QList<FormattedText> parseAnsi(const QString &text, const QTextCharFormat &format);
|
||||
void dumpIncompleteLine(const QString &line, LogFormat format);
|
||||
void flushIncompleteLine();
|
||||
void clearLastLine();
|
||||
void scroll();
|
||||
|
||||
private:
|
||||
AnsiEscape m_escapeCodeHandler;
|
||||
QPlainTextEdit *m_output;
|
||||
QTextCursor m_cursor;
|
||||
QTextCharFormat m_formats[NumberOfFormats];
|
||||
QPair<QString, LogFormat> m_incompleteLine;
|
||||
bool m_prependLineFeed;
|
||||
bool m_prependCarriageReturn;
|
||||
};
|
@ -2,6 +2,7 @@
|
||||
#include "app_data.hpp"
|
||||
#include "game_models.hpp"
|
||||
#include "game_settings_dialog.hpp"
|
||||
#include "log_formatter.hpp"
|
||||
#include "pkg.hpp"
|
||||
#include "progress_dialog.hpp"
|
||||
#include "settings.hpp"
|
||||
@ -92,18 +93,21 @@ MainWindow::MainWindow() :
|
||||
m_tab->addTab(m_games, QIcon(":/resources/view-comfy.svg"), "Games");
|
||||
|
||||
// Setup log view.
|
||||
m_log = new QPlainTextEdit();
|
||||
m_log->setReadOnly(true);
|
||||
m_log->setLineWrapMode(QPlainTextEdit::NoWrap);
|
||||
m_log->setMaximumBlockCount(10000);
|
||||
auto log = new QPlainTextEdit();
|
||||
|
||||
log->setReadOnly(true);
|
||||
log->setLineWrapMode(QPlainTextEdit::NoWrap);
|
||||
log->setMaximumBlockCount(10000);
|
||||
|
||||
#ifdef _WIN32
|
||||
m_log->document()->setDefaultFont(QFont("Courier New", 10));
|
||||
log->document()->setDefaultFont(QFont("Courier New", 10));
|
||||
#else
|
||||
m_log->document()->setDefaultFont(QFont("monospace", 10));
|
||||
log->document()->setDefaultFont(QFont("monospace", 10));
|
||||
#endif
|
||||
|
||||
m_tab->addTab(m_log, QIcon(":/resources/card-text-outline.svg"), "Log");
|
||||
m_log = new LogFormatter(log, this);
|
||||
|
||||
m_tab->addTab(log, QIcon(":/resources/card-text-outline.svg"), "Log");
|
||||
|
||||
// Setup status bar.
|
||||
statusBar();
|
||||
@ -333,7 +337,7 @@ void MainWindow::startGame(const QModelIndex &index)
|
||||
auto game = model->get(index.row()); // Qt already guaranteed the index is valid.
|
||||
|
||||
// Clear previous log and switch to log view.
|
||||
m_log->clear();
|
||||
m_log->reset();
|
||||
m_tab->setCurrentIndex(1);
|
||||
|
||||
// Get full path to kernel binary.
|
||||
@ -423,18 +427,19 @@ void MainWindow::kernelOutput()
|
||||
// It is possible for Qt to signal this slot after QProcess::errorOccurred or QProcess::finished
|
||||
// so we need to check if the those signals has been received.
|
||||
while (m_kernel && m_kernel->canReadLine()) {
|
||||
auto line = m_kernel->readLine();
|
||||
auto line = QString::fromUtf8(m_kernel->readLine());
|
||||
|
||||
if (line.endsWith('\n')) {
|
||||
line.chop(1);
|
||||
}
|
||||
|
||||
m_log->appendPlainText(QString::fromUtf8(line));
|
||||
m_log->appendMessage(line, InfoMessageFormat);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::kernelTerminated(int, QProcess::ExitStatus)
|
||||
{
|
||||
// Do nothing if we got QProcess::errorOccurred before this signal.
|
||||
if (!m_kernel) {
|
||||
return;
|
||||
}
|
||||
|
||||
kernelOutput();
|
||||
|
||||
QMessageBox::critical(this, "Error", "The emulator kernel has been stopped unexpectedly. Please take a look on the log and report the issue.");
|
||||
|
@ -3,8 +3,8 @@
|
||||
#include <QMainWindow>
|
||||
#include <QProcess>
|
||||
|
||||
class LogFormatter;
|
||||
class QListView;
|
||||
class QPlainTextEdit;
|
||||
|
||||
class MainWindow final : public QMainWindow {
|
||||
public:
|
||||
@ -36,6 +36,6 @@ private:
|
||||
private:
|
||||
QTabWidget *m_tab;
|
||||
QListView *m_games;
|
||||
QPlainTextEdit *m_log;
|
||||
LogFormatter *m_log;
|
||||
QProcess *m_kernel;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user