mirror of
https://github.com/arabine/open-story-teller.git
synced 2025-12-06 17:09:06 +01:00
497 lines
14 KiB
C++
497 lines
14 KiB
C++
|
|
|
|
#include <QAction>
|
|
#include <QScreen>
|
|
#include <QtWidgets/QApplication>
|
|
#include <QBoxLayout>
|
|
#include <QGridLayout>
|
|
#include <QGroupBox>
|
|
#include <QLabel>
|
|
#include <QMainWindow>
|
|
#include <QDockWidget>
|
|
#include <QMenuBar>
|
|
#include <QToolBar>
|
|
#include <QFileDialog>
|
|
#include <QJsonArray>
|
|
#include <QMimeDatabase>
|
|
#include <QMessageBox>
|
|
#include <QStatusBar>
|
|
#include <QLineEdit>
|
|
#include <QWidgetAction>
|
|
#include <QTreeWidget>
|
|
#include <QHeaderView>
|
|
#include <QSettings>
|
|
#include <QtDebug>
|
|
#include <QStandardPaths>
|
|
#include <QDir>
|
|
|
|
#include <QtNodes/BasicGraphicsScene>
|
|
#include <QtNodes/ConnectionStyle>
|
|
#include <QtNodes/GraphicsView>
|
|
#include <QtNodes/StyleCollection>
|
|
#include <QtNodes/Definitions>
|
|
|
|
#include <QtNodes/UndoCommands>
|
|
|
|
#include "main_window.h"
|
|
#include "media_node_model.h"
|
|
|
|
using QtNodes::CreateCommand;
|
|
using QtNodes::BasicGraphicsScene;
|
|
using QtNodes::ConnectionStyle;
|
|
using QtNodes::GraphicsView;
|
|
using QtNodes::NodeRole;
|
|
using QtNodes::StyleCollection;
|
|
|
|
int nodeX = 0.0;
|
|
|
|
typedef void (*message_output_t)(QtMsgType , const QMessageLogContext &, const QString &);
|
|
|
|
MainWindow::MainWindow()
|
|
: m_model(m_project)
|
|
, m_scene(m_model)
|
|
{
|
|
SetupTemporaryProject();
|
|
|
|
RefreshProjectInformation();
|
|
|
|
Callback<void(QtMsgType , const QMessageLogContext &, const QString &)>::func = std::bind(&MainWindow::MessageOutput, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
|
|
auto cb = static_cast<message_output_t>(Callback<void(QtMsgType , const QMessageLogContext &, const QString &)>::callback);
|
|
|
|
qInstallMessageHandler(cb);
|
|
|
|
m_toolbar = new ToolBar();
|
|
m_scene.setDropShadowEffect(false);
|
|
m_scene.nodeGeometry().setMarginsRatio(0.02);
|
|
m_toolbar->createActions(menuBar());
|
|
addToolBar(m_toolbar);
|
|
createStatusBar();
|
|
|
|
m_logDock = new LogDock();
|
|
addDockWidget(Qt::DockWidgetArea::BottomDockWidgetArea, m_logDock);
|
|
m_toolbar->AddDockToMenu(m_logDock->toggleViewAction());
|
|
|
|
m_nodeEditorDock = new NodeEditorDock(&m_scene);
|
|
addDockWidget(Qt::DockWidgetArea::LeftDockWidgetArea, m_nodeEditorDock);
|
|
m_toolbar->AddDockToMenu(m_nodeEditorDock->toggleViewAction());
|
|
|
|
m_ostHmiDock = new OstHmiDock();
|
|
addDockWidget(Qt::DockWidgetArea::RightDockWidgetArea, m_ostHmiDock);
|
|
m_toolbar->AddDockToMenu(m_ostHmiDock->toggleViewAction());
|
|
|
|
connect(m_ostHmiDock, &OstHmiDock::sigOkButton, [=]() {
|
|
QCoreApplication::postEvent(this, new VmEvent(VmEvent::evOkButton));
|
|
});
|
|
|
|
m_resourcesDock = new ResourcesDock(m_project);
|
|
addDockWidget(Qt::DockWidgetArea::RightDockWidgetArea, m_resourcesDock);
|
|
m_toolbar->AddDockToMenu(m_resourcesDock->toggleViewAction());
|
|
|
|
m_scriptEditorDock = new ScriptEditorDock();
|
|
addDockWidget(Qt::DockWidgetArea::LeftDockWidgetArea, m_scriptEditorDock);
|
|
m_toolbar->AddDockToMenu(m_scriptEditorDock->toggleViewAction());
|
|
|
|
m_vmDock = new VmDock(m_assembler);
|
|
addDockWidget(Qt::DockWidgetArea::RightDockWidgetArea, m_vmDock);
|
|
m_toolbar->AddDockToMenu(m_vmDock->toggleViewAction());
|
|
|
|
connect(m_vmDock, &VmDock::sigCompile, [=]() {
|
|
|
|
|
|
m_resourcesDock->SaveToProject();
|
|
m_scriptEditorDock->setScript(m_project.Compile());
|
|
});
|
|
|
|
connect(m_vmDock, &VmDock::sigStepInstruction, [=]() {
|
|
QCoreApplication::postEvent(this, new VmEvent(VmEvent::evStep));
|
|
});
|
|
|
|
connect(m_vmDock, &VmDock::sigBuild, [=]() {
|
|
buildScript();
|
|
});
|
|
|
|
m_ramView = new MemoryViewDock("RamViewDock", "RAM");
|
|
addDockWidget(Qt::DockWidgetArea::RightDockWidgetArea, m_ramView);
|
|
m_toolbar->AddDockToMenu(m_ramView->toggleViewAction());
|
|
|
|
m_romView = new MemoryViewDock("RomViewDock", "ROM");
|
|
addDockWidget(Qt::DockWidgetArea::RightDockWidgetArea, m_romView);
|
|
m_toolbar->AddDockToMenu(m_romView->toggleViewAction());
|
|
|
|
m_chooseFileDialog = new QDialog(this);
|
|
m_chooseFileUi.setupUi(m_chooseFileDialog);
|
|
m_chooseFileDialog->close();
|
|
|
|
connect(&m_model, &StoryGraphModel::sigChooseFile, [&](NodeId id) {
|
|
m_chooseFileUi.tableView->setModel(&m_resourcesDock->getModel());
|
|
m_chooseFileDialog->exec();
|
|
|
|
// Get the file choosen
|
|
QModelIndexList selection = m_chooseFileUi.tableView->selectionModel()->selectedRows();
|
|
|
|
if (selection.count() > 0)
|
|
{
|
|
// Take first
|
|
QModelIndex index = selection.at(0);
|
|
QString fn = m_resourcesDock->getModel().GetFileName(index.row());
|
|
QJsonObject obj;
|
|
obj["image"] = fn;
|
|
m_model.setNodeData(id, NodeRole::InternalData, obj.toVariantMap());
|
|
}
|
|
});
|
|
|
|
// TODO: merge both
|
|
m_model.registerNode<MediaNodeModel>("MediaNode");
|
|
m_model.addModel("MediaNode", "Story Teller");
|
|
|
|
// m_project.Load("packs/BE8949F60D854F54828419A1BDAED36A/pack.json");
|
|
|
|
// DisplayNode(m_project.m_tree, QtNodes::InvalidNodeId);
|
|
|
|
// VM Initialize
|
|
|
|
m_chip32_ctx.stack_size = 512;
|
|
|
|
m_chip32_ctx.rom.mem = m_rom_data;
|
|
m_chip32_ctx.rom.addr = 0;
|
|
m_chip32_ctx.rom.size = sizeof(m_rom_data);
|
|
|
|
m_chip32_ctx.ram.mem = m_ram_data;
|
|
m_chip32_ctx.ram.addr = sizeof(m_rom_data);
|
|
m_chip32_ctx.ram.size = sizeof(m_ram_data);
|
|
|
|
|
|
Callback<uint8_t(uint8_t)>::func = std::bind(&MainWindow::Syscall, this, std::placeholders::_1);
|
|
m_chip32_ctx.syscall = static_cast<syscall_t>(Callback<uint8_t(uint8_t)>::callback);
|
|
|
|
readSettings();
|
|
|
|
connect(m_toolbar, &ToolBar::sigNew, this, [=]() {
|
|
NewProject();
|
|
});
|
|
|
|
connect(m_toolbar, &ToolBar::sigSave, this, [=]() {
|
|
SaveProject();
|
|
});
|
|
|
|
qDebug() << "Started StoryTeller Editor";
|
|
}
|
|
|
|
void MainWindow::CloseProject()
|
|
{
|
|
|
|
}
|
|
|
|
void MainWindow::SetupTemporaryProject()
|
|
{
|
|
QString appDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
|
|
|
// Generate a project unique ID name
|
|
m_project.uuid = QUuid::createUuid().toString().toStdString();
|
|
m_project.working_dir = QString(appDir + QDir::separator() + m_project.uuid.c_str()).toStdString();
|
|
m_project.Initialize();
|
|
|
|
// m_resourcesDock->Initialize();
|
|
|
|
qDebug() << "Working dir is: " << m_project.working_dir.c_str();
|
|
}
|
|
|
|
void MainWindow::RefreshProjectInformation()
|
|
{
|
|
setWindowTitle(QString("StoryTeller Editor - ") + m_project.working_dir.c_str());
|
|
}
|
|
|
|
void MainWindow::MessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
|
|
{
|
|
m_logDock->Append(type, context, msg);
|
|
}
|
|
|
|
void MainWindow::readSettings()
|
|
{
|
|
QSettings settings("D8S", "OpenStoryTeller");
|
|
restoreGeometry(settings.value("MainWindow/geometry").toByteArray());
|
|
restoreState(settings.value("MainWindow/windowState").toByteArray());
|
|
}
|
|
|
|
void MainWindow::closeEvent(QCloseEvent *event)
|
|
{
|
|
QSettings settings("D8S", "OpenStoryTeller");
|
|
settings.setValue("MainWindow/geometry", saveGeometry());
|
|
settings.setValue("MainWindow/windowState", saveState());
|
|
QMainWindow::closeEvent(event);
|
|
}
|
|
|
|
void MainWindow::DisplayNode(StoryNode *m_tree, QtNodes::NodeId parentId)
|
|
{
|
|
QJsonObject nodeInternalData;
|
|
|
|
if (m_tree->image >= 0)
|
|
{
|
|
std::string imageFile = m_project.working_dir + "/rf/" + m_project.m_images[m_tree->image].file + ".bmp";
|
|
std::cout << "Node image: " << imageFile << std::endl;
|
|
nodeInternalData["image"] = imageFile.c_str();
|
|
}
|
|
else
|
|
{
|
|
nodeInternalData["image"] = "";
|
|
}
|
|
|
|
if (m_tree->children.size() > 1)
|
|
{
|
|
auto delegate = m_model.createNode("MediaNode");
|
|
QtNodes::NodeId id = m_model.addNode(delegate);
|
|
m_model.setNodeData(id, NodeRole::Position, QPointF(nodeX, 0));
|
|
delegate->load(nodeInternalData);
|
|
nodeX += 100;
|
|
|
|
if ((id != QtNodes::InvalidNodeId) && (parentId != QtNodes::InvalidNodeId))
|
|
{
|
|
m_model.addConnection(QtNodes::ConnectionId{parentId, 0, id, 0});
|
|
}
|
|
|
|
for (int child_n = 0; child_n < m_tree->children.size(); child_n++)
|
|
{
|
|
DisplayNode(m_tree->children[child_n], id);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
auto delegate = std::make_shared<MediaNodeModel>(m_model);
|
|
QtNodes::NodeId id = m_model.addNode(delegate);
|
|
m_model.setNodeData(id, NodeRole::Position, QPointF(nodeX, 0));
|
|
// m_model.setNodeData(id, NodeRole::InternalData, nodeInternalData);
|
|
delegate->load(nodeInternalData);
|
|
|
|
nodeX += 100;
|
|
|
|
if ((id != QtNodes::InvalidNodeId) && (parentId != QtNodes::InvalidNodeId))
|
|
{
|
|
m_model.addConnection(QtNodes::ConnectionId{parentId, 0, id, 0});
|
|
}
|
|
|
|
if (m_tree->children.size() == 1)
|
|
{
|
|
DisplayNode(m_tree->children[0], id);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void MainWindow::highlightNextLine()
|
|
{
|
|
uint32_t pcVal = m_chip32_ctx.registers[PC];
|
|
|
|
// On recherche quelle est la ligne qui possède une instruction à cette adresse
|
|
std::vector<Chip32::Instr>::const_iterator ptr = m_assembler.Begin();
|
|
for (; ptr != m_assembler.End(); ++ptr)
|
|
{
|
|
if ((ptr->addr == pcVal) && ptr->isRomCode())
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ptr != m_assembler.End())
|
|
{
|
|
m_dbg.line = (ptr->line - 1);
|
|
m_scriptEditorDock->HighlightLine(m_dbg.line);
|
|
}
|
|
else
|
|
{
|
|
// Not found
|
|
qDebug() << "Reached end or instruction not found: " << m_dbg.line;
|
|
}
|
|
}
|
|
|
|
void MainWindow::updateAll()
|
|
{
|
|
m_vmDock->updateRegistersView(m_chip32_ctx);
|
|
highlightNextLine();
|
|
// Refresh RAM content
|
|
m_ramView->SetMemory(m_ram_data, m_chip32_ctx.ram.size);
|
|
}
|
|
|
|
QString MainWindow::GetFileName(uint32_t addr)
|
|
{
|
|
char strBuf[100];
|
|
bool isRam = addr & 0x80000000;
|
|
addr &= 0xFFFF; // mask the RAM/ROM bit, ensure 16-bit addressing
|
|
if (isRam) {
|
|
strcpy(&strBuf[0], (const char *)&m_chip32_ctx.ram.mem[addr]);
|
|
} else {
|
|
strcpy(&strBuf[0], (const char *)&m_chip32_ctx.rom.mem[addr]);
|
|
}
|
|
return QString(strBuf);
|
|
}
|
|
|
|
bool MainWindow::event(QEvent *event)
|
|
{
|
|
if (event->type() == VmEvent::evStep)
|
|
{
|
|
VmEvent *myEvent = static_cast<VmEvent *>(event);
|
|
|
|
if (m_dbg.run_result == VM_OK)
|
|
{
|
|
stepInstruction();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else if (event->type() == VmEvent::evOkButton)
|
|
{
|
|
VmEvent *myEvent = static_cast<VmEvent *>(event);
|
|
|
|
if (m_dbg.run_result == VM_WAIT_EVENT)
|
|
{
|
|
// Result event is in R1
|
|
m_chip32_ctx.registers[R1] = 0x01;
|
|
stepInstruction();
|
|
}
|
|
|
|
}
|
|
|
|
// false means it should be send to target also. as in , we dont remove it.
|
|
// if you return true , you will take the event and widget never sees it so be carefull with that.
|
|
return QMainWindow::event(event);
|
|
}
|
|
|
|
uint8_t MainWindow::Syscall(uint8_t code)
|
|
{
|
|
uint8_t retCode = 0;
|
|
qDebug() << "SYSCALL: " << (int)code;
|
|
|
|
// Media
|
|
if (code == 1)
|
|
{
|
|
// image file name address is in R0
|
|
QString imageFile = GetFileName(m_chip32_ctx.registers[R0]);
|
|
// sound file name address is in R1
|
|
QString soundFile = GetFileName(m_chip32_ctx.registers[R1]);
|
|
|
|
qDebug() << "Image: " << imageFile << ", Sound: " << soundFile;
|
|
m_ostHmiDock->SetImage(imageFile);
|
|
}
|
|
// WAIT EVENT bits:
|
|
// 0: block
|
|
// 1: OK button
|
|
// 2: home button
|
|
// 3: pause button
|
|
// 4: rotary left
|
|
// 5: rotary right
|
|
else if (code == 2)
|
|
{
|
|
// Event mask is located in R0
|
|
// optional timeout is located in R1
|
|
// if timeout is set to zero, wait for infinite and beyond
|
|
retCode = 1; // set the VM in pause
|
|
}
|
|
|
|
|
|
return retCode;
|
|
}
|
|
|
|
void MainWindow::stepInstruction()
|
|
{
|
|
m_dbg.run_result = chip32_step(&m_chip32_ctx);
|
|
updateAll();
|
|
}
|
|
|
|
void MainWindow::buildScript()
|
|
{
|
|
if (m_assembler.Parse(m_scriptEditorDock->getScript().toStdString()) == true )
|
|
{
|
|
if (m_assembler.BuildBinary(m_program, m_result) == true)
|
|
{
|
|
m_result.Print();
|
|
|
|
// Update ROM memory
|
|
std::copy(m_program.begin(), m_program.end(), m_rom_data);
|
|
m_ramView->SetMemory(m_ram_data, sizeof(m_ram_data));
|
|
m_romView->SetMemory(m_rom_data, m_program.size());
|
|
chip32_initialize(&m_chip32_ctx);
|
|
m_dbg.run_result = VM_OK;
|
|
updateAll();
|
|
DebugContext::DumpCodeAssembler(m_assembler);
|
|
}
|
|
else
|
|
{
|
|
qCritical() << m_assembler.GetLastError().c_str();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
qCritical() << m_assembler.GetLastError().c_str();
|
|
}
|
|
}
|
|
|
|
void MainWindow::createStatusBar()
|
|
{
|
|
statusBar()->showMessage(tr("Ready"));
|
|
}
|
|
|
|
void MainWindow::NewProject()
|
|
{
|
|
|
|
}
|
|
|
|
|
|
void MainWindow::open()
|
|
{
|
|
QMimeDatabase mimeDatabase;
|
|
QString fileName = QFileDialog::getOpenFileName(this,
|
|
tr("Choose a file name"), ".",
|
|
mimeDatabase.mimeTypeForName("application/json").filterString());
|
|
if (fileName.isEmpty())
|
|
return;
|
|
QFile file(fileName);
|
|
if (!file.open(QFile::ReadOnly | QFile::Text)) {
|
|
QMessageBox::warning(this, tr("Dock Widgets"),
|
|
tr("Cannot write file %1:\n%2.")
|
|
.arg(QDir::toNativeSeparators(fileName), file.errorString()));
|
|
return;
|
|
}
|
|
|
|
QTextStream in(&file);
|
|
QGuiApplication::setOverrideCursor(Qt::WaitCursor);
|
|
|
|
QJsonParseError error;
|
|
QString projectData = in.readAll();
|
|
|
|
QJsonDocument dataValue = QJsonDocument::fromJson(projectData.toUtf8(), &error);
|
|
|
|
m_model.load(dataValue.object());
|
|
|
|
QGuiApplication::restoreOverrideCursor();
|
|
}
|
|
|
|
void MainWindow::SaveProject()
|
|
{
|
|
// Open the dialog if the project file does not exists
|
|
if (!QFile::exists(m_project.file_path.c_str()))
|
|
{
|
|
// Save current project
|
|
QString fileName = QFileDialog::getSaveFileName(this, tr("Save project file"),
|
|
QDir::homePath() + "/new_story.ostproj",
|
|
tr("OpenStory Teller project (*.ostproj)"));
|
|
if (fileName.isEmpty())
|
|
return;
|
|
m_project.file_path = fileName.toStdString();
|
|
}
|
|
|
|
QJsonObject saveData = m_model.save();
|
|
QJsonDocument doc(saveData);
|
|
qDebug() << doc.toJson();
|
|
|
|
statusBar()->showMessage(tr("Saved '%1'").arg(m_project.file_path.c_str()), 2000);
|
|
}
|
|
|
|
void MainWindow::about()
|
|
{
|
|
QMessageBox::about(this, tr("About OST Editor"),
|
|
tr("OpenStoryTeller node editor."
|
|
"Build your own stories on an open source hardware."));
|
|
}
|
|
|
|
|
|
|