diff --git a/hecl-gui/CMakeLists.txt b/hecl-gui/CMakeLists.txt index e69de29bb..760d4303f 100644 --- a/hecl-gui/CMakeLists.txt +++ b/hecl-gui/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 3.10) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) + +find_package(Qt5Widgets) + +add_executable(hecl-gui + MainWindow.ui MainWindow.hpp MainWindow.cpp + FileDirDialog.hpp FileDirDialog.cpp + main.cpp) + +target_link_libraries(hecl-gui ${Qt5Widgets_LIBRARIES}) diff --git a/hecl-gui/FileDirDialog.cpp b/hecl-gui/FileDirDialog.cpp new file mode 100644 index 000000000..92d34ff3e --- /dev/null +++ b/hecl-gui/FileDirDialog.cpp @@ -0,0 +1,68 @@ +#include "FileDirDialog.hpp" +#include +#include +#include +#include +#include +#include + +FileDirDialog::FileDirDialog(QWidget *parent) + : QFileDialog(parent) +{ + m_selectedFiles.clear(); + + this->setOption(QFileDialog::DontUseNativeDialog, true); + this->setFileMode(QFileDialog::Directory); + QList btns = this->findChildren(); + for (int i = 0; i < btns.size(); ++i) + { + QString text = btns[i]->text(); + if (text.toLower().contains("open") || text.toLower().contains("choose")) + { + m_btnOpen = btns[i]; + break; + } + } + + if (!m_btnOpen) + return; + + m_btnOpen->installEventFilter(this); + m_btnOpen->disconnect(SIGNAL(clicked())); + connect(m_btnOpen, SIGNAL(clicked()), this, SLOT(chooseClicked())); + + + m_listView = findChild("listView"); + if (m_listView) { + m_listView->setSelectionMode(QAbstractItemView::ExtendedSelection); + } + + m_treeView = findChild(); + if (m_treeView) + m_treeView->setSelectionMode(QAbstractItemView::ExtendedSelection); +} + +bool FileDirDialog::eventFilter( QObject* watched, QEvent* event ) +{ + QPushButton *btn = qobject_cast(watched); + if (btn && !btn->isEnabled() && event->type()==QEvent::EnabledChange) + btn->setEnabled(true); + + return QWidget::eventFilter(watched, event); +} + + +void FileDirDialog::chooseClicked() +{ + QModelIndexList indexList = m_listView->selectionModel()->selectedIndexes(); + foreach (QModelIndex index, indexList) + if (index.column( )== 0) + m_selectedFiles.append(this->directory().absolutePath() + "/" + index.data().toString()); + + QDialog::accept(); +} + +QStringList FileDirDialog::selectedFiles() +{ + return m_selectedFiles; +} diff --git a/hecl-gui/FileDirDialog.hpp b/hecl-gui/FileDirDialog.hpp new file mode 100644 index 000000000..39763b76e --- /dev/null +++ b/hecl-gui/FileDirDialog.hpp @@ -0,0 +1,30 @@ +#ifndef FILEDIRDIALOG_HPP +#define FILEDIRDIALOG_HPP + +#include +#include +#include +#include + +class QPushButton; +class QTreeView; +class QListView; + +class FileDirDialog : public QFileDialog +{ + Q_OBJECT +private: + QListView* m_listView = nullptr; + QTreeView* m_treeView = nullptr; + QPushButton* m_btnOpen = nullptr; + QStringList m_selectedFiles; + +public slots: + void chooseClicked(); +public: + FileDirDialog(QWidget* parent = nullptr); + QStringList selectedFiles(); + bool eventFilter(QObject* watched, QEvent* event); +}; + +#endif // FILEDIRDIALOG_HPP diff --git a/hecl-gui/MainWindow.cpp b/hecl-gui/MainWindow.cpp index e69de29bb..b5e49fc7e 100644 --- a/hecl-gui/MainWindow.cpp +++ b/hecl-gui/MainWindow.cpp @@ -0,0 +1,640 @@ +#include "MainWindow.hpp" +#include "ui_MainWindow.h" +#include +#include +#include +#include +#include +#include "FileDirDialog.hpp" + +MainWindow::MainWindow(QWidget *parent) : + QMainWindow(parent), + m_ui(new Ui::MainWindow) + , m_heclProc(this) +{ + m_ui->setupUi(this); + m_ui->heclTabs->setCurrentIndex(0); + m_ui->splitter->setSizes({0,-1}); + initSlots(); +} + +MainWindow::~MainWindow() +{ + m_heclProc.close(); + m_heclProc.terminate(); + delete m_ui; +} +/* TODO: more complete Vt102 emulation */ +// based on information: http://en.m.wikipedia.org/wiki/ANSI_escape_code http://misc.flogisoft.com/bash/tip_colors_and_formatting http://invisible-island.net/xterm/ctlseqs/ctlseqs.html +void MainWindow::parseEscapeSequence(int attribute, QListIterator< QString > & i, QTextCharFormat & textCharFormat, QTextCharFormat const & defaultTextCharFormat) +{ + switch (attribute) { + case 0 : { // Normal/Default (reset all attributes) + textCharFormat = defaultTextCharFormat; + break; + } + case 1 : { // Bold/Bright (bold or increased intensity) + textCharFormat.setFontWeight(QFont::Bold); + break; + } + case 2 : { // Dim/Faint (decreased intensity) + textCharFormat.setFontWeight(QFont::Light); + break; + } + case 3 : { // Italicized (italic on) + textCharFormat.setFontItalic(true); + break; + } + case 4 : { // Underscore (single underlined) + textCharFormat.setUnderlineStyle(QTextCharFormat::SingleUnderline); + textCharFormat.setFontUnderline(true); + break; + } + case 5 : { // Blink (slow, appears as Bold) + textCharFormat.setFontWeight(QFont::Bold); + break; + } + case 6 : { // Blink (rapid, appears as very Bold) + textCharFormat.setFontWeight(QFont::Black); + break; + } + case 7 : { // Reverse/Inverse (swap foreground and background) + QBrush foregroundBrush = textCharFormat.foreground(); + textCharFormat.setForeground(textCharFormat.background()); + textCharFormat.setBackground(foregroundBrush); + break; + } + case 8 : { // Concealed/Hidden/Invisible (usefull for passwords) + textCharFormat.setForeground(textCharFormat.background()); + break; + } + case 9 : { // Crossed-out characters + textCharFormat.setFontStrikeOut(true); + break; + } + case 10 : { // Primary (default) font + textCharFormat.setFont(defaultTextCharFormat.font()); + break; + } + case 11: + case 12: + case 13: + case 14: + case 15: + case 16: + case 17: + case 18: + case 19: { + QFontDatabase fontDatabase; + QString fontFamily = textCharFormat.fontFamily(); + QStringList fontStyles = fontDatabase.styles(fontFamily); + int fontStyleIndex = attribute - 11; + if (fontStyleIndex < fontStyles.length()) { + textCharFormat.setFont(fontDatabase.font(fontFamily, fontStyles.at(fontStyleIndex), textCharFormat.font().pointSize())); + } + break; + } + case 20 : { // Fraktur (unsupported) + break; + } + case 21 : { // Set Bold off + textCharFormat.setFontWeight(QFont::Normal); + break; + } + case 22 : { // Set Dim off + textCharFormat.setFontWeight(QFont::Normal); + break; + } + case 23 : { // Unset italic and unset fraktur + textCharFormat.setFontItalic(false); + break; + } + case 24 : { // Unset underlining + textCharFormat.setUnderlineStyle(QTextCharFormat::NoUnderline); + textCharFormat.setFontUnderline(false); + break; + } + case 25 : { // Unset Blink/Bold + textCharFormat.setFontWeight(QFont::Normal); + break; + } + case 26 : { // Reserved + break; + } + case 27 : { // Positive (non-inverted) + QBrush backgroundBrush = textCharFormat.background(); + textCharFormat.setBackground(textCharFormat.foreground()); + textCharFormat.setForeground(backgroundBrush); + break; + } + case 28 : { + textCharFormat.setForeground(defaultTextCharFormat.foreground()); + textCharFormat.setBackground(defaultTextCharFormat.background()); + break; + } + case 29 : { + textCharFormat.setUnderlineStyle(QTextCharFormat::NoUnderline); + textCharFormat.setFontUnderline(false); + break; + } + case 30: + case 31: + case 32: + case 33: + case 34: + case 35: + case 36: + case 37: + { + int colorIndex = attribute - 30; + QColor color; + if (QFont::Normal < textCharFormat.fontWeight()) { + switch (colorIndex) { + case 0 : { + color = Qt::darkGray; + break; + } + case 1 : { + color = Qt::red; + break; + } + case 2 : { + color = Qt::green; + break; + } + case 3 : { + color = Qt::yellow; + break; + } + case 4 : { + color = Qt::blue; + break; + } + case 5 : { + color = Qt::magenta; + break; + } + case 6 : { + color = Qt::cyan; + break; + } + case 7 : { + color = Qt::white; + break; + } + default : { + Q_ASSERT(false); + } + } + } else { + switch (colorIndex) { + case 0 : { + color = Qt::black; + break; + } + case 1 : { + color = Qt::darkRed; + break; + } + case 2 : { + color = Qt::darkGreen; + break; + } + case 3 : { + color = Qt::darkYellow; + break; + } + case 4 : { + color = Qt::darkBlue; + break; + } + case 5 : { + color = Qt::darkMagenta; + break; + } + case 6 : { + color = Qt::darkCyan; + break; + } + case 7 : { + color = Qt::lightGray; + break; + } + default : { + Q_ASSERT(false); + } + } + } + textCharFormat.setForeground(color); + break; + } + case 38 : { + if (i.hasNext()) { + bool ok = false; + int selector = i.next().toInt(&ok); + Q_ASSERT(ok); + QColor color; + switch (selector) { + case 2 : { + if (!i.hasNext()) { + break; + } + int red = i.next().toInt(&ok); + Q_ASSERT(ok); + if (!i.hasNext()) { + break; + } + int green = i.next().toInt(&ok); + Q_ASSERT(ok); + if (!i.hasNext()) { + break; + } + int blue = i.next().toInt(&ok); + Q_ASSERT(ok); + color.setRgb(red, green, blue); + break; + } + case 5 : + { + if (!i.hasNext()) { + break; + } + int index = i.next().toInt(&ok); + Q_ASSERT(ok); + if (index >= 0 && index <= 0x07) + { // 0x00-0x07: standard colors (as in ESC [ 30..37 m) + return parseEscapeSequence(index - 0x00 + 30, i, textCharFormat, defaultTextCharFormat); + } + else if (index >= 0x08 && index <= 0x0F) + { // 0x08-0x0F: high intensity colors (as in ESC [ 90..97 m) + return parseEscapeSequence(index - 0x08 + 90, i, textCharFormat, defaultTextCharFormat); + } + else if (index >= 0x10 && index <= 0xE7) + { // 0x10-0xE7: 6*6*6=216 colors: 16 + 36*r + 6*g + b (0≤r,g,b≤5) + index -= 0x10; + int red = index % 6; + index /= 6; + int green = index % 6; + index /= 6; + int blue = index % 6; + index /= 6; + Q_ASSERT(index == 0); + color.setRgb(red, green, blue); + break; + } + else if (index >= 0xE8 && index <= 0xFF) + { // 0xE8-0xFF: grayscale from black to white in 24 steps + qreal intensity = qreal(index - 0xE8) / (0xFF - 0xE8); + color.setRgbF(intensity, intensity, intensity); + break; + } + textCharFormat.setForeground(color); + break; + } + default : { + break; + } + } + } + break; + } + case 39 : { + textCharFormat.setForeground(defaultTextCharFormat.foreground()); + break; + } + case 40: + case 41: + case 42: + case 43: + case 44: + case 45: + case 46: + case 47: { + int colorIndex = attribute - 40; + QColor color; + switch (colorIndex) { + case 0 : { + color = Qt::darkGray; + break; + } + case 1 : { + color = Qt::red; + break; + } + case 2 : { + color = Qt::green; + break; + } + case 3 : { + color = Qt::yellow; + break; + } + case 4 : { + color = Qt::blue; + break; + } + case 5 : { + color = Qt::magenta; + break; + } + case 6 : { + color = Qt::cyan; + break; + } + case 7 : { + color = Qt::white; + break; + } + default : { + Q_ASSERT(false); + } + } + textCharFormat.setBackground(color); + break; + } + case 48 : { + if (i.hasNext()) { + bool ok = false; + int selector = i.next().toInt(&ok); + Q_ASSERT(ok); + QColor color; + switch (selector) { + case 2 : { + if (!i.hasNext()) { + break; + } + int red = i.next().toInt(&ok); + Q_ASSERT(ok); + if (!i.hasNext()) { + break; + } + int green = i.next().toInt(&ok); + Q_ASSERT(ok); + if (!i.hasNext()) { + break; + } + int blue = i.next().toInt(&ok); + Q_ASSERT(ok); + color.setRgb(red, green, blue); + break; + } + case 5 : { + if (!i.hasNext()) { + break; + } + int index = i.next().toInt(&ok); + Q_ASSERT(ok); + if (index >= 0x00 && index <= 0x07) + { // 0x00-0x07: standard colors (as in ESC [ 40..47 m) + return parseEscapeSequence(index - 0x00 + 40, i, textCharFormat, defaultTextCharFormat); + } + else if (index >= 0x08 && index <= 0x0F) + { // 0x08-0x0F: high intensity colors (as in ESC [ 100..107 m) + return parseEscapeSequence(index - 0x08 + 100, i, textCharFormat, defaultTextCharFormat); + } + else if (index >= 0x10 && index <= 0xE7) + { // 0x10-0xE7: 6*6*6=216 colors: 16 + 36*r + 6*g + b (0≤r,g,b≤5) + index -= 0x10; + int red = index % 6; + index /= 6; + int green = index % 6; + index /= 6; + int blue = index % 6; + index /= 6; + Q_ASSERT(index == 0); + color.setRgb(red, green, blue); + break; + } + else if (index >= 0xE8 && index <= 0xFF) + { // 0xE8-0xFF: grayscale from black to white in 24 steps + qreal intensity = qreal(index - 0xE8) / (0xFF - 0xE8); + color.setRgbF(intensity, intensity, intensity); + } + } + textCharFormat.setBackground(color); + break; + } + } + break; + } + case 49: { + textCharFormat.setBackground(defaultTextCharFormat.background()); + break; + } + case 90: + case 91: + case 92: + case 93: + case 94: + case 95: + case 96: + case 97: { + int colorIndex = attribute - 90; + QColor color; + switch (colorIndex) { + case 0 : { + color = Qt::darkGray; + break; + } + case 1 : { + color = Qt::red; + break; + } + case 2 : { + color = Qt::green; + break; + } + case 3 : { + color = Qt::yellow; + break; + } + case 4 : { + color = Qt::blue; + break; + } + case 5 : { + color = Qt::magenta; + break; + } + case 6 : { + color = Qt::cyan; + break; + } + case 7 : { + color = Qt::white; + break; + } + default : { + Q_ASSERT(false); + } + } + color.setRedF(color.redF() * 0.8); + color.setGreenF(color.greenF() * 0.8); + color.setBlueF(color.blueF() * 0.8); + textCharFormat.setForeground(color); + break; + } + case 100: + case 101: + case 102: + case 103: + case 104: + case 105: + case 106: + case 107: + { + int colorIndex = attribute - 100; + QColor color; + switch (colorIndex) { + case 0 : { + color = Qt::darkGray; + break; + } + case 1 : { + color = Qt::red; + break; + } + case 2 : { + color = Qt::green; + break; + } + case 3 : { + color = Qt::yellow; + break; + } + case 4 : { + color = Qt::blue; + break; + } + case 5 : { + color = Qt::magenta; + break; + } + case 6 : { + color = Qt::cyan; + break; + } + case 7 : { + color = Qt::white; + break; + } + default : { + Q_ASSERT(false); + } + } + color.setRedF(color.redF() * 0.8); + color.setGreenF(color.greenF() * 0.8); + color.setBlueF(color.blueF() * 0.8); + textCharFormat.setBackground(color); + break; + } + default : { + break; + } + } +} + +void MainWindow::onExtract() +{ + +} + +void MainWindow::onReturnPressed() +{ + if (sender() == m_ui->pathEdit && !m_ui->pathEdit->text().isEmpty()) + m_path = m_ui->pathEdit->text(); +} + +void MainWindow::initSlots() +{ +#ifdef Q_OS_WIN + m_heclProc.setEnvironment(QProcessEnvironment::systemEnvironment().toStringList() + QStringList("ConEmuANSI=ON")); +#endif + connect(&m_heclProc, &QProcess::readyRead, [=](){ + m_ansiString = m_heclProc.readAll(); + m_ui->processOutput->clear(); + setTextTermFormatting(m_ui->processOutput, m_ansiString); + m_ui->processOutput->ensureCursorVisible(); + }); + + connect(m_ui->extractBtn, &QPushButton::clicked, [=](){ + if (m_path.isEmpty()) + return; + QString path = QFileInfo(m_path).absolutePath(); + m_ansiString.clear(); + m_heclProc.close(); + m_heclProc.terminate(); + m_heclProc.setProcessChannelMode(QProcess::ProcessChannelMode::MergedChannels); + m_heclProc.setWorkingDirectory(path); + m_heclProc.start("../hecl/driver/hecl.exe", {"extract", m_path, "-y"}); + }); + + connect(m_ui->browseBtn, &QPushButton::clicked, [=]() { + FileDirDialog dialog(this); + dialog.setWindowTitle("Select ISO or Directory"); + int res = dialog.exec();//QFileDialog::getOpenFileName(this, "Select ISO or Directory"); + if (res == QFileDialog::Rejected) + return; + + if (dialog.selectedFiles().size() <= 0) + return; + + QString path = dialog.selectedFiles().at(0); + /* TODO: Add beacon detection */ + if (!path.isEmpty()) + { + m_path = path; + m_ui->pathEdit->setText(m_path); + m_ui->extractBtn->setEnabled(true); + m_ui->packageBtn->setEnabled(true); + } + }); + + connect(m_ui->packageBtn, &QPushButton::clicked, [=](){ + if (m_path.isEmpty()) + return; + QString projectDir = m_path; + if (projectDir.endsWith(".iso", Qt::CaseInsensitive)) + projectDir.remove(m_path.length() - 4, 4); + + m_ansiString.clear(); + m_heclProc.close(); + m_heclProc.terminate(); + m_heclProc.setWorkingDirectory(projectDir); + m_heclProc.setProcessChannelMode(QProcess::ProcessChannelMode::MergedChannels); + m_heclProc.start("../hecl/driver/hecl.exe", {"package", "-y"}); + }); +} + +void MainWindow::setTextTermFormatting(QTextEdit* textEdit, const QString& text) +{ + QTextDocument * document = textEdit->document(); + QRegExp const escapeSequenceExpression(R"(\x1B\[([\d;]+)m)"); + QTextCursor cursor(document); + QTextCharFormat defaultTextCharFormat = cursor.charFormat(); + cursor.beginEditBlock(); + int offset = escapeSequenceExpression.indexIn(text); + cursor.insertText(text.mid(0, offset)); + QTextCharFormat textCharFormat = defaultTextCharFormat; + while (!(offset < 0)) { + int previousOffset = offset + escapeSequenceExpression.matchedLength(); + QStringList capturedTexts = escapeSequenceExpression.capturedTexts().back().split(';'); + QListIterator< QString > i(capturedTexts); + while (i.hasNext()) { + bool ok = false; + int attribute = i.next().toInt(&ok); + Q_ASSERT(ok); + parseEscapeSequence(attribute, i, textCharFormat, defaultTextCharFormat); + } + offset = escapeSequenceExpression.indexIn(text, previousOffset); + if (offset < 0) { + cursor.insertText(text.mid(previousOffset), textCharFormat); + } else { + cursor.insertText(text.mid(previousOffset, offset - previousOffset), textCharFormat); + } + } + cursor.setCharFormat(defaultTextCharFormat); + cursor.endEditBlock(); + cursor.movePosition(QTextCursor::End); + textEdit->setTextCursor(cursor); +} diff --git a/hecl-gui/MainWindow.hpp b/hecl-gui/MainWindow.hpp index e69de29bb..b1eb66d2a 100644 --- a/hecl-gui/MainWindow.hpp +++ b/hecl-gui/MainWindow.hpp @@ -0,0 +1,33 @@ +#ifndef MAINWINDOW_HPP +#define MAINWINDOW_HPP + +#include +#include +#include +class QTextEdit; +class QTextCharFormat; + +namespace Ui { +class MainWindow; +} + +class MainWindow : public QMainWindow +{ + Q_OBJECT + Ui::MainWindow* m_ui; + QString m_ansiString; + QString m_path; + QProcess m_heclProc; +public: + explicit MainWindow(QWidget *parent = 0); + ~MainWindow(); + void setTextTermFormatting(QTextEdit* textEdit, QString const & text); + void parseEscapeSequence(int attribute, QListIterator< QString > & i, QTextCharFormat & textCharFormat, QTextCharFormat const & defaultTextCharFormat); +private slots: + void onExtract(); + void onReturnPressed(); +private: + void initSlots(); +}; + +#endif // MAINWINDOW_HPP diff --git a/hecl-gui/MainWindow.ui b/hecl-gui/MainWindow.ui index 3da5ac070..975db76ae 100644 --- a/hecl-gui/MainWindow.ui +++ b/hecl-gui/MainWindow.ui @@ -21,66 +21,16 @@ - - - - Qt::Horizontal - - - - 166 - 20 - - - - - - - - &Extract - - - - - - - &Package - - - - - - - false - - - &Launch - - - - - - - Qt::Horizontal - - - - 167 - 20 - - - - - 2 + 0 Data - + 2 @@ -93,13 +43,97 @@ 2 - + Qt::Vertical - - + + + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + + + 120 + 120 + 120 + + + + + + + 240 + 240 + 240 + + + + + + + + + Terminal + 10 + + + + true + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Terminal'; font-size:10pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> + + + Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + @@ -134,6 +168,62 @@ + + + + Qt::Horizontal + + + + 166 + 20 + + + + + + + + false + + + &Extract + + + + + + + false + + + &Package + + + + + + + false + + + &Launch + + + + + + + Qt::Horizontal + + + + 167 + 20 + + + + @@ -148,12 +238,12 @@ Working Directory: - lineEdit + pathEdit - + 0 @@ -169,13 +259,19 @@ - + 0 0 + + + 32 + 16777215 + + ... @@ -188,5 +284,25 @@ - + + + pathEdit + returnPressed() + MainWindow + onReturnPressed() + + + 331 + 20 + + + 302 + 305 + + + + + + onReturnPressed() +