From fc46659e423dad3d00fa87de1c3c3aba5476d35f Mon Sep 17 00:00:00 2001 From: Anthony Rabine Date: Thu, 11 May 2023 12:16:59 +0200 Subject: [PATCH] Open/load project --- story-editor/example.asm | 96 +++++++++++ story-editor/src/dock_widget_base.cpp | 23 ++- story-editor/src/dock_widget_base.h | 9 +- story-editor/src/log_dock.cpp | 4 +- story-editor/src/main_window.cpp | 202 +++++++++++++++--------- story-editor/src/main_window.h | 7 +- story-editor/src/memory_view_dock.cpp | 2 +- story-editor/src/ost-hmi.ui | 2 +- story-editor/src/osthmi_dock.cpp | 2 +- story-editor/src/resources_dock.cpp | 2 +- story-editor/src/script_editor_dock.cpp | 104 +----------- story-editor/src/story_graph_model.cpp | 60 +++++-- story-editor/src/story_graph_model.h | 1 + story-editor/src/story_node_base.h | 4 + story-editor/src/story_project.cpp | 2 +- story-editor/src/story_project.h | 8 +- story-editor/src/toolbar.cpp | 46 +++--- story-editor/src/toolbar.h | 10 +- story-editor/src/vm_dock.cpp | 2 +- 19 files changed, 361 insertions(+), 225 deletions(-) create mode 100644 story-editor/example.asm diff --git a/story-editor/example.asm b/story-editor/example.asm new file mode 100644 index 0000000..2a29af7 --- /dev/null +++ b/story-editor/example.asm @@ -0,0 +1,96 @@ +; jump over the data, to our entry label + jump .entry + +; Constant elements are separated by commas +$imageBird DC8, "example.bmp", 8 ; string of chars, followed by one byte +$soundChoice DC8, "choose1.snd" +$yaya DC8, "yaya.bmp" +$rabbit DC8, "rabbit.bmp" +$someConstant DC32, 12456789 + +; Liste des noeuds à appeler +$ChoiceObject DC32, 2, .MEDIA_02, .MEDIA_03 + +; DVsxx to declare a variable in RAM, followed by the number of elements +$MyArray DV8, 10 ; array of 10 bytes +$RamData1 DV32, 1 ; one 32-bit integer +$ChoiceMem DV32, 10 ; 10 elements for the choices, to be generated + +; label definition +.entry: ;; comment here should work + + + ; Syscall test: show image and play sound + lcons r0, $imageBird ; image name address in ROM located in R0 (null terminated) + lcons r1, $soundChoice ; set to 0 if no sound + syscall 1 + lcons r0, $ChoiceObject + jump .media ; no return possible, so a jump is enough + +; Generic media choice manager +.media: + ; Les adresses des différents medias sont dans la stack +; Arguments: + ; r0: address d'une structure de type "media choice" +; Local: + ; t0: loop counter + ; t1: increment 1 + ; t2: increment 4 + ; t3: current media address + +.media_loop_start: + load t0, @r0, 4 ; Le premier élément est le nombre de choix possibles, t0 = 3 (exemple) + lcons t1, 1 + lcons t2, 4 + mov t3, r0 +.media_loop: + add t3, t2 ; @++ + + +; ------- On appelle un autre media node + push r0 ; save r0 + load r0, @t3, 4 ; r0 = content in ram at address in T4 + call r0 + pop r0 + ; TODO: wait for event + + sub t0, t1 ; i-- + skipnz t0 ; if (r0) goto start_loop; + jump .media_loop_start + jump .media_loop + +.MEDIA_02: + lcons r0, $yaya ; image name address in ROM located in R0 (null terminated) + lcons r1, $soundChoice ; set to 0 if no sound + syscall 1 + ret + +.MEDIA_03: + lcons r0, $rabbit + lcons r1, $soundChoice + syscall 1 + ret + +.SYSCALL_TEST: + ; syscall test: wait for event + lcons r0, 0xFF ; wait for all event, blocking + syscall 2 + +; We create a stupid loop just for RAM variable testing + + lcons r0, 4 ; prepare loop: 4 iterations + lcons r6, $RamData1 ; store address to R6 + store @r6, r0, 4 ; save R0 in RAM + lcons r1, 1 +.loop: + load r0, @r6, 4 ; load this variable + sub r0, r1 + store @r6, r0, 4 ; save R0 in RAM + skipz r0 ; skip loop if R0 == 0 + jump .loop + + + mov r0, r2 ; copy R2 into R0 (blank space between , and R2) +mov R0,R2 ; copy R2 into R0 (NO blank space between , and R2) + + halt diff --git a/story-editor/src/dock_widget_base.cpp b/story-editor/src/dock_widget_base.cpp index 5cf74ac..e11f96f 100644 --- a/story-editor/src/dock_widget_base.cpp +++ b/story-editor/src/dock_widget_base.cpp @@ -17,9 +17,30 @@ bool EventFilter::eventFilter( QObject *obj, QEvent *event ) } -DockWidgetBase::DockWidgetBase(const QString &title) +DockWidgetBase::DockWidgetBase(const QString &title, bool visibility) : QDockWidget(title) + , m_visibility(visibility ? tribool::True : tribool::False) { EventFilter* filter = new EventFilter( this ); installEventFilter( filter ); + setAllowedAreas(Qt::AllDockWidgetAreas); } + +void DockWidgetBase::Close() +{ + // Memorize prefered visibility +// m_visibility = isVisible() ? tribool::True : tribool::False; + hide(); +} + +void DockWidgetBase::Open() +{ + // Restore prefered visibility + if (m_visibility) { + show(); + } else { + hide(); + } +} + + diff --git a/story-editor/src/dock_widget_base.h b/story-editor/src/dock_widget_base.h index 9bfa239..cd12855 100644 --- a/story-editor/src/dock_widget_base.h +++ b/story-editor/src/dock_widget_base.h @@ -17,7 +17,14 @@ class DockWidgetBase : public QDockWidget { Q_OBJECT public: - DockWidgetBase(const QString &title); + DockWidgetBase(const QString &title, bool visibility); + + void Close(); + void Open(); + +private: + enum tribool: uint8_t {False = 0, True = 1, Unknown = 2}; + tribool m_visibility{Unknown}; }; #endif // DOCK_WIDGET_BASE_H diff --git a/story-editor/src/log_dock.cpp b/story-editor/src/log_dock.cpp index 1dfec7d..bdbcf07 100644 --- a/story-editor/src/log_dock.cpp +++ b/story-editor/src/log_dock.cpp @@ -1,9 +1,9 @@ #include "log_dock.h" LogDock::LogDock() - : DockWidgetBase(tr("Logs")) + : DockWidgetBase(tr("Logs"), true) { - setObjectName("OstHmiDock"); // used to save the state + setObjectName("LogsDock"); // used to save the state m_logUi.setupUi(this); } diff --git a/story-editor/src/main_window.cpp b/story-editor/src/main_window.cpp index 7227ad2..f3aa7b0 100644 --- a/story-editor/src/main_window.cpp +++ b/story-editor/src/main_window.cpp @@ -51,12 +51,12 @@ typedef void (*message_output_t)(QtMsgType , const QMessageLogContext &, const Q MainWindow::MainWindow() : m_model(m_project) , m_scene(m_model) - , m_settings("D8S", "OpenStoryTeller") + , m_settings("OpenStoryTeller", "OpenStoryTellerEditor") { m_project.Clear(); - // SetupTemporaryProject(); -// RefreshProjectInformation(); + m_scene.setDropShadowEffect(false); + m_scene.nodeGeometry().setMarginsRatio(0.02); m_view = new GraphicsView(&m_scene); m_view->setScaleRange(0, 0); @@ -64,10 +64,9 @@ MainWindow::MainWindow() setCentralWidget(m_view); m_toolbar = new ToolBar(); - m_scene.setDropShadowEffect(false); - m_scene.nodeGeometry().setMarginsRatio(0.02); m_toolbar->createActions(menuBar()); - addToolBar(m_toolbar); + addToolBar(Qt::LeftToolBarArea, m_toolbar); + m_toolbar->setVisible(true); connect(m_toolbar, &ToolBar::sigDefaultDocksPosition, this, &MainWindow::slotDefaultDocksPosition); @@ -118,6 +117,9 @@ MainWindow::MainWindow() addDockWidget(Qt::DockWidgetArea::RightDockWidgetArea, m_romView); m_toolbar->AddDockToMenu(m_romView->toggleViewAction()); + tabifyDockWidget(m_vmDock, m_romView); + tabifyDockWidget(m_romView, m_ramView); + m_chooseFileDialog = new QDialog(this); m_chooseFileUi.setupUi(m_chooseFileDialog); m_chooseFileDialog->close(); @@ -167,8 +169,6 @@ MainWindow::MainWindow() Callback::func = std::bind(&MainWindow::Syscall, this, std::placeholders::_1); m_chip32_ctx.syscall = static_cast(Callback::callback); - readSettings(); - connect(m_toolbar, &ToolBar::sigNew, this, [&]() { NewProject(); }); @@ -178,7 +178,7 @@ MainWindow::MainWindow() }); connect(m_toolbar, &ToolBar::sigOpen, this, [&]() { - OpenProject(); + OpenProjectDialog(); }); connect(m_toolbar, &ToolBar::sigClose, this, [&]() { @@ -189,14 +189,21 @@ MainWindow::MainWindow() ExitProgram(); }); + connect(m_toolbar, &ToolBar::sigOpenRecent, this, [&](const QString &recent) { + CloseProject(); + OpenProject(recent); + }); // Install event handler now that everythin is initialized Callback::func = std::bind(&MainWindow::MessageOutput, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3); auto cb = static_cast(Callback::callback); + qInstallMessageHandler(cb); - qDebug() << "Started StoryTeller Editor"; + readSettings(); + qDebug() << "Settings location: " << m_settings.fileName(); + qDebug() << "Welcome to StoryTeller Editor"; CloseProject(); @@ -216,26 +223,6 @@ void MainWindow::slotWelcome() msgBox.exec(); } -void MainWindow::SetupTemporaryProject() -{ - /* - QString appDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); - - // Look at any previous workspace used - // Generate a project unique ID name if no any - m_settings.setValue("project/workspace", m_project.uuid.c_str()); - - m_project.working_dir = QString(appDir + QDir::separator() + m_project.uuid.c_str()).toStdString(); - m_project.name = "Untitled project"; - m_project.Initialize(m_settings.value("project/workspace", QUuid::createUuid().toString()).toString().toStdString() - ); - - // m_resourcesDock->Initialize(); - - qDebug() << "Working dir is: " << m_project.working_dir.c_str(); -*/ -} - void MainWindow::ExitProgram() { // FIXME: warn if project not saved @@ -243,7 +230,7 @@ void MainWindow::ExitProgram() void MainWindow::RefreshProjectInformation() { - setWindowTitle(QString("StoryTeller Editor - ") + m_project.GetWorkingDir().c_str()); + setWindowTitle(QString("StoryTeller Editor - ") + m_project.GetProjectFilePath().c_str()); } void MainWindow::MessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg) @@ -258,12 +245,20 @@ void MainWindow::readSettings() restoreGeometry(m_settings.value("MainWindow/geometry").toByteArray()); restoreState(m_settings.value("MainWindow/windowState").toByteArray()); + + // Restore recent projects list + m_recentProjects = m_settings.value("RecentProjects").toStringList(); + m_toolbar->GenerateRecentProjectsMenu(m_recentProjects); } void MainWindow::closeEvent(QCloseEvent *event) { m_settings.setValue("MainWindow/geometry", saveGeometry()); m_settings.setValue("MainWindow/windowState", saveState()); + + // Memorize recent projects list + m_settings.setValue("RecentProjects", m_recentProjects); + QMainWindow::closeEvent(event); } @@ -485,11 +480,11 @@ void MainWindow::NewProject() m_project.SetDisplayFormat(s.width(), s.height()); m_project.SetImageFormat(m_newProjectDialog->GetImageFormat()); m_project.SetSoundFormat(m_newProjectDialog->GetSoundFormat()); + m_project.SetName(m_newProjectDialog->GetProjectName().toStdString()); + m_project.SetUuid(QUuid::createUuid().toString().toStdString()); - m_toolbar->SetAllDocks(true); - m_toolbar->SetActionsActive(true); - m_toolbar->ShowAllDocks(true); - m_view->setEnabled(true); + SaveProject(); + EnableProject(); } } @@ -497,51 +492,124 @@ void MainWindow::CloseProject() { m_project.Clear(); - m_toolbar->SetAllDocks(false); + m_model.Clear(); + + m_ostHmiDock->Close(); + m_resourcesDock->Close(); + m_scriptEditorDock->Close(); + m_vmDock->Close(); + m_ramView->Close(); + m_romView->Close(); + m_logDock->Close(); + m_toolbar->SetActionsActive(false); m_view->setEnabled(false); } -void MainWindow::OpenProject() +void MainWindow::EnableProject() { - QString fn = QFileDialog::getOpenFileName(this, tr("Open project file"), - QDir::homePath(), - tr("StoryEditor Project (project.json)")); + // Add to recent if not exists + if (!m_recentProjects.contains(m_project.GetProjectFilePath().c_str())) + { + m_recentProjects.push_front(m_project.GetProjectFilePath().c_str()); + // Limit to 10 recent projects + if (m_recentProjects.size() > 10) { + m_recentProjects.pop_back(); + } + m_toolbar->GenerateRecentProjectsMenu(m_recentProjects); + } - m_project.Initialize(fn.toStdString()); + m_ostHmiDock->Open(); + m_resourcesDock->Open(); + m_scriptEditorDock->Open(); + m_vmDock->Open(); + m_ramView->Open(); + m_romView->Open(); + m_logDock->Open(); + + m_toolbar->SetActionsActive(true); + m_view->setEnabled(true); +} + +void MainWindow::OpenProjectDialog() +{ + QFileDialog dialog(this); + + dialog.setNameFilter(tr("StoryEditor Project (project.json)")); + dialog.setFileMode(QFileDialog::ExistingFile); + dialog.setDirectory(QDir::homePath()); + + if (dialog.exec()) + { + QStringList fileNames = dialog.selectedFiles(); + + if (fileNames.size() == 1) + { + OpenProject(fileNames.at(0)); + } + } +} + +void MainWindow::OpenProject(const QString &filePath) +{ + bool success = false; + QString errorMsg; + + m_project.Initialize(filePath.toStdString()); QFile loadFile(m_project.GetProjectFilePath().c_str()); - if (!loadFile.open(QIODevice::ReadOnly)) { - qWarning("Couldn't open project file."); - return; - } - - QJsonParseError err; - QJsonDocument loadDoc = QJsonDocument::fromJson(loadFile.readAll(), &err); - - if (err.error == QJsonParseError::NoError) + if (loadFile.open(QIODevice::ReadOnly)) { - QJsonObject projectRoot = loadDoc.object(); - QString errorString; + QJsonParseError err; + QJsonDocument loadDoc = QJsonDocument::fromJson(loadFile.readAll(), &err); - if (projectRoot.contains("project")) + if (err.error == QJsonParseError::NoError) { - QJsonObject projectData = projectRoot["project"].toObject(); + QJsonObject projectRoot = loadDoc.object(); - m_project.name = projectData["name"].toString().toStdString(); -// m_project.uuid = projectData["uuid"].toString().toStdString(); + if (projectRoot.contains("project")) + { + QJsonObject projectData = projectRoot["project"].toObject(); + + m_project.SetName(projectData["name"].toString().toStdString()); + m_project.SetUuid(projectData["uuid"].toString().toStdString()); + + if (projectRoot.contains("nodegraph")) + { + QJsonObject nodeData = projectRoot["nodegraph"].toObject(); + m_model.load(nodeData); + + success = true; + } + else + { + errorMsg = tr("Missing nodegraph section in JSON file."); + } + } + else + { + errorMsg = tr("Missing project section in JSON file."); + } } - - if (projectRoot.contains("nodegraph")) + else { - QJsonObject nodeData = projectRoot["nodegraph"].toObject(); - m_model.load(nodeData); + errorMsg = err.errorString(); } } else { - QMessageBox::critical(this, tr("Open project error"), err.errorString()); + errorMsg = tr("Could not open project file."); + } + + if (success) + { + EnableProject(); + } + else + { + qWarning() << errorMsg; + QMessageBox::critical(this, tr("Open project error"), errorMsg); } } @@ -551,8 +619,8 @@ void MainWindow::SaveProject() QJsonObject jsonModel = m_model.save(); QJsonObject projectData; - projectData["name"] = m_project.name.c_str(); -// projectData["uuid"] = m_project.uuid.c_str(); + projectData["name"] = m_project.GetName().c_str(); + projectData["uuid"] = m_project.GetUuid().c_str(); QJsonObject saveData; saveData["project"] = projectData; @@ -572,12 +640,4 @@ void MainWindow::SaveProject() statusBar()->showMessage(tr("Saved '%1'").arg(m_project.GetProjectFilePath().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.")); -} - - diff --git a/story-editor/src/main_window.h b/story-editor/src/main_window.h index f7d4869..37395f9 100644 --- a/story-editor/src/main_window.h +++ b/story-editor/src/main_window.h @@ -141,6 +141,7 @@ private: QDialog *m_chooseFileDialog; Ui::chooseFileDIalog m_chooseFileUi; NewProjectDialog *m_newProjectDialog{nullptr}; + QStringList m_recentProjects; // VM uint8_t m_rom_data[16*1024]; @@ -158,7 +159,6 @@ private: void createStatusBar(); void SaveProject(); void DisplayNode(StoryNode *m_tree, QtNodes::NodeId parentId); - void about(); void buildScript(); void highlightNextLine(); void readSettings(); @@ -170,11 +170,12 @@ private: void MessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg); void NewProject(); - void SetupTemporaryProject(); void RefreshProjectInformation(); void CloseProject(); - void OpenProject(); + void OpenProjectDialog(); void ExitProgram(); + void EnableProject(); + void OpenProject(const QString &filePath); }; #endif // MAIN_WINDOW_H diff --git a/story-editor/src/memory_view_dock.cpp b/story-editor/src/memory_view_dock.cpp index 8637841..47c543b 100644 --- a/story-editor/src/memory_view_dock.cpp +++ b/story-editor/src/memory_view_dock.cpp @@ -2,7 +2,7 @@ #include "model/buffer/qmemorybuffer.h" MemoryViewDock::MemoryViewDock(const QString &objectName, const QString &title) - : DockWidgetBase(title) + : DockWidgetBase(title, false) { setObjectName(objectName); diff --git a/story-editor/src/ost-hmi.ui b/story-editor/src/ost-hmi.ui index 1fd0e68..de636a1 100644 --- a/story-editor/src/ost-hmi.ui +++ b/story-editor/src/ost-hmi.ui @@ -33,7 +33,7 @@ QFrame::Box - TextLabel + diff --git a/story-editor/src/osthmi_dock.cpp b/story-editor/src/osthmi_dock.cpp index 175255a..54b589f 100644 --- a/story-editor/src/osthmi_dock.cpp +++ b/story-editor/src/osthmi_dock.cpp @@ -3,7 +3,7 @@ #include OstHmiDock::OstHmiDock() - : DockWidgetBase(tr("StoryTeller HMI")) + : DockWidgetBase(tr("StoryTeller HMI"), true) { setObjectName("OstHmiDock"); // used to save the state m_uiOstDisplay.setupUi(this); diff --git a/story-editor/src/resources_dock.cpp b/story-editor/src/resources_dock.cpp index c4cefbe..149c04d 100644 --- a/story-editor/src/resources_dock.cpp +++ b/story-editor/src/resources_dock.cpp @@ -4,7 +4,7 @@ ResourcesDock::ResourcesDock(StoryProject &project) : m_project(project) - , DockWidgetBase(tr("Resources")) + , DockWidgetBase(tr("Resources"), true) { setObjectName("ResourcesDock"); // used to save the state diff --git a/story-editor/src/script_editor_dock.cpp b/story-editor/src/script_editor_dock.cpp index 2a3c721..989822e 100644 --- a/story-editor/src/script_editor_dock.cpp +++ b/story-editor/src/script_editor_dock.cpp @@ -1,114 +1,12 @@ #include "script_editor_dock.h" -static const std::string test1 = R"( -; jump over the data, to our entry label - jump .entry - -; Constant elements are separated by commas -$imageBird DC8, "example.bmp", 8 ; string of chars, followed by one byte -$soundChoice DC8, "choose1.snd" -$yaya DC8, "yaya.bmp" -$rabbit DC8, "rabbit.bmp" -$someConstant DC32, 12456789 - -; Liste des noeuds à appeler -$ChoiceObject DC32, 2, .MEDIA_02, .MEDIA_03 - -; DVsxx to declare a variable in RAM, followed by the number of elements -$MyArray DV8, 10 ; array of 10 bytes -$RamData1 DV32, 1 ; one 32-bit integer -$ChoiceMem DV32, 10 ; 10 elements for the choices, to be generated - -; label definition -.entry: ;; comment here should work - - - ; Syscall test: show image and play sound - lcons r0, $imageBird ; image name address in ROM located in R0 (null terminated) - lcons r1, $soundChoice ; set to 0 if no sound - syscall 1 - lcons r0, $ChoiceObject - jump .media ; no return possible, so a jump is enough - -; Generic media choice manager -.media: - ; Les adresses des différents medias sont dans la stack -; Arguments: - ; r0: address d'une structure de type "media choice" -; Local: - ; t0: loop counter - ; t1: increment 1 - ; t2: increment 4 - ; t3: current media address - -.media_loop_start: - load t0, @r0, 4 ; Le premier élément est le nombre de choix possibles, t0 = 3 (exemple) - lcons t1, 1 - lcons t2, 4 - mov t3, r0 -.media_loop: - add t3, t2 ; @++ - - -; ------- On appelle un autre media node - push r0 ; save r0 - load r0, @t3, 4 ; r0 = content in ram at address in T4 - call r0 - pop r0 - ; TODO: wait for event - - sub t0, t1 ; i-- - skipnz t0 ; if (r0) goto start_loop; - jump .media_loop_start - jump .media_loop - -.MEDIA_02: - lcons r0, $yaya ; image name address in ROM located in R0 (null terminated) - lcons r1, $soundChoice ; set to 0 if no sound - syscall 1 - ret - -.MEDIA_03: - lcons r0, $rabbit - lcons r1, $soundChoice - syscall 1 - ret - -.SYSCALL_TEST: - ; syscall test: wait for event - lcons r0, 0xFF ; wait for all event, blocking - syscall 2 - -; We create a stupid loop just for RAM variable testing - - lcons r0, 4 ; prepare loop: 4 iterations - lcons r6, $RamData1 ; store address to R6 - store @r6, r0, 4 ; save R0 in RAM - lcons r1, 1 -.loop: - load r0, @r6, 4 ; load this variable - sub r0, r1 - store @r6, r0, 4 ; save R0 in RAM - skipz r0 ; skip loop if R0 == 0 - jump .loop - - - mov r0, r2 ; copy R2 into R0 (blank space between , and R2) -mov R0,R2 ; copy R2 into R0 (NO blank space between , and R2) - - halt - -)"; - ScriptEditorDock::ScriptEditorDock() - : DockWidgetBase(tr("Script editor")) + : DockWidgetBase(tr("Script editor"), false) { setObjectName("ScriptEditorDock"); // used to save the state m_editor = new CodeEditor(this); - - m_editor->setPlainText(test1.c_str()); m_highlighter = new Highlighter(m_editor->document()); setWidget(m_editor); } diff --git a/story-editor/src/story_graph_model.cpp b/story-editor/src/story_graph_model.cpp index 3a666e5..28a77b7 100644 --- a/story-editor/src/story_graph_model.cpp +++ b/story-editor/src/story_graph_model.cpp @@ -310,11 +310,15 @@ bool StoryGraphModel::deleteNode(NodeId const nodeId) QJsonObject StoryGraphModel::saveNode(NodeId const nodeId) const { QJsonObject nodeJson; - - nodeJson["id"] = static_cast(nodeId); - nodeJson["internal-data"] = _models.at(nodeId)->save(); + auto it = _models.find(nodeId); + if (it == _models.end()) + return nodeJson; + + auto &model = it->second; + + nodeJson["internal-data"] = model->save(); { QPointF const pos = nodeData(nodeId, NodeRole::Position).value(); @@ -352,27 +356,49 @@ QJsonObject StoryGraphModel::save() const void StoryGraphModel::loadNode(QJsonObject const &nodeJson) { - NodeId restoredNodeId = static_cast(nodeJson["id"].toInt()); + // Possibility of the id clash when reading it from json and not generating a + // new value. + // 1. When restoring a scene from a file. + // Conflict is not possible because the scene must be cleared by the time of + // loading. + // 2. When undoing the deletion command. Conflict is not possible + // because all the new ids were created past the removed nodes. + NodeId restoredNodeId = nodeJson["id"].toInt(); _nextNodeId = std::max(_nextNodeId, restoredNodeId + 1); - // Create new node. - _nodeIds.insert(restoredNodeId); + QJsonObject const internalDataJson = nodeJson["internal-data"].toObject(); - setNodeData(restoredNodeId, NodeRole::InPortCount, nodeJson["inPortCount"].toString().toUInt()); + QString delegateModelName = internalDataJson["model-name"].toString(); - setNodeData(restoredNodeId, - NodeRole::OutPortCount, - nodeJson["outPortCount"].toString().toUInt()); +// std::unique_ptr model = _registry->create(delegateModelName); + + auto model = createNode(delegateModelName.toStdString()); + + if (model) { +// connect(model.get(), +// &NodeDelegateModel::dataUpdated, +// [restoredNodeId, this](PortIndex const portIndex) { +// onOutPortDataUpdated(restoredNodeId, portIndex); +// }); + + + _models[restoredNodeId] = model; + model->setNodeId(restoredNodeId); + _nodeIds.insert(restoredNodeId); + + Q_EMIT nodeCreated(restoredNodeId); - { QJsonObject posJson = nodeJson["position"].toObject(); QPointF const pos(posJson["x"].toDouble(), posJson["y"].toDouble()); setNodeData(restoredNodeId, NodeRole::Position, pos); - } - Q_EMIT nodeCreated(restoredNodeId); + _models[restoredNodeId]->load(internalDataJson); + } else { + throw std::logic_error(std::string("No registered model with name ") + + delegateModelName.toLocal8Bit().data()); + } } void StoryGraphModel::load(QJsonObject const &jsonDocument) @@ -419,3 +445,11 @@ void StoryGraphModel::removePort(NodeId nodeId, PortType portType, PortIndex por portsDeleted(); } + +void StoryGraphModel::Clear() +{ + _nodeIds.clear(); + _connectivity.clear(); + _models.clear(); + emit modelReset(); +} diff --git a/story-editor/src/story_graph_model.h b/story-editor/src/story_graph_model.h index c8b0cce..f84d46a 100644 --- a/story-editor/src/story_graph_model.h +++ b/story-editor/src/story_graph_model.h @@ -131,6 +131,7 @@ public: } StoryProject &GetProject() { return m_project; }; + void Clear(); signals: void sigChooseFile(NodeId id); diff --git a/story-editor/src/story_node_base.h b/story-editor/src/story_node_base.h index bbb6aab..4bf1745 100644 --- a/story-editor/src/story_node_base.h +++ b/story-editor/src/story_node_base.h @@ -25,6 +25,10 @@ public: QPointF pos; }; + ~StoryNodeBase() { + std::cout << "Delete node: " << m_nodeId << std::endl; + } + void setNodeId(NodeId id) { m_nodeId = id; } NodeId getNodeId() { return m_nodeId; } diff --git a/story-editor/src/story_project.cpp b/story-editor/src/story_project.cpp index f8a41d2..0301bf9 100644 --- a/story-editor/src/story_project.cpp +++ b/story-editor/src/story_project.cpp @@ -101,7 +101,7 @@ bool StoryProject::Load(const std::string &file_path) m_type = j["type"]; m_code = j["code"]; - name = j["name"]; + m_name = j["name"]; success = true; diff --git a/story-editor/src/story_project.h b/story-editor/src/story_project.h index 13e38ef..f30e5fe 100644 --- a/story-editor/src/story_project.h +++ b/story-editor/src/story_project.h @@ -47,8 +47,6 @@ struct StoryProject enum ImageFormat { IMG_FORMAT_BMP_4BITS, IMG_FORMAT_QOIF, IMG_FORMAT_COUNT }; enum SoundFormat { SND_FORMAT_WAV, SND_FORMAT_QOAF, SND_FORMAT_COUNT }; - // Project properties and location - std::string name; /// human readable name std::vector m_nodes; std::string m_type; @@ -75,9 +73,13 @@ struct StoryProject void SetImageFormat(ImageFormat format); void SetSoundFormat(SoundFormat format); void SetDisplayFormat(int w, int h); + void SetName(const std::string &name) { m_name = name; } + void SetUuid(const std::string &uuid) { m_uuid = uuid; } std::string GetProjectFilePath() const; std::string GetWorkingDir() const; + std::string GetName() const { return m_name; } + std::string GetUuid() const { return m_uuid; } std::filesystem::path ImagesPath() const { return m_imagesPath; } std::filesystem::path SoundsPath() const { return m_soundsPath; } @@ -95,6 +97,8 @@ public: static std::string ToUpper(const std::string &input); private: + // Project properties and location + std::string m_name; /// human readable name std::string m_uuid; std::filesystem::path m_imagesPath; std::filesystem::path m_soundsPath; diff --git a/story-editor/src/toolbar.cpp b/story-editor/src/toolbar.cpp index 3345bb7..a5590c9 100644 --- a/story-editor/src/toolbar.cpp +++ b/story-editor/src/toolbar.cpp @@ -1,9 +1,14 @@ #include "toolbar.h" #include "qmenubar.h" +#include + ToolBar::ToolBar() { setObjectName("MainToolBar"); + +// setIconSize(QSize(10, 10)); +// setFixedHeight(36); } void ToolBar::createActions(QMenuBar *menuBar) @@ -14,7 +19,7 @@ void ToolBar::createActions(QMenuBar *menuBar) { QIcon icon(":/assets/file-document-plus-outline.svg"); QAction *act = new QAction(icon, tr("&New project"), this); - act->setShortcuts(QKeySequence::Save); + act->setShortcuts(QKeySequence::New); act->setStatusTip(tr("Create a new project")); connect(act, &QAction::triggered, this, &ToolBar::sigNew); fileMenu->addAction(act); @@ -51,6 +56,9 @@ void ToolBar::createActions(QMenuBar *menuBar) addAction(m_closeProjectAction); } + m_recentProjectsMenu = new QMenu(tr("Recent projects")); + fileMenu->addMenu(m_recentProjectsMenu); + fileMenu->addSeparator(); QAction *quitAct = fileMenu->addAction(tr("&Quit"), this, &ToolBar::sigExit); @@ -64,20 +72,27 @@ void ToolBar::createActions(QMenuBar *menuBar) auto act = m_windowsMenu->addAction(tr("Reset docks position")); connect(act, &QAction::triggered, this, &ToolBar::sigDefaultDocksPosition); - m_closeAllDocksAction = m_windowsMenu->addAction(tr("Show/Hide all docks")); - m_closeAllDocksAction->setCheckable(true); - connect(m_closeAllDocksAction, &QAction::triggered, this, [=] (bool checked) { - SetAllDocks(checked); - }); - m_windowsMenu->addSeparator(); QMenu *helpMenu = menuBar->addMenu(tr("&Help")); - QAction *aboutAct = helpMenu->addAction(tr("&About"), this, &ToolBar::sigAbout); + QAction *aboutAct = helpMenu->addAction(tr("&About"), this, &ToolBar::slotAbout); aboutAct->setStatusTip(tr("Show the application's About box")); } +void ToolBar::GenerateRecentProjectsMenu(const QStringList &recents) +{ + m_recentProjectsMenu->clear(); + + for (auto &r: recents) + { + auto act = m_recentProjectsMenu->addAction(r); + connect(act, &QAction::triggered, this, [&, r]() { + emit sigOpenRecent(r); + }); + } +} + void ToolBar::SetActionsActive(bool enable) { for (auto d : m_actionDockList) @@ -89,19 +104,12 @@ void ToolBar::SetActionsActive(bool enable) m_saveProjectAction->setEnabled(enable); } -void ToolBar::ShowAllDocks(bool enable) -{ - m_closeAllDocksAction->setEnabled(enable); - m_closeAllDocksAction->trigger(); -} -void ToolBar::SetAllDocks(bool enable) +void ToolBar::slotAbout() { - for (auto d : m_actionDockList) - { - d->setChecked(!enable); - d->trigger(); - } + QMessageBox::about(this, tr("About OST Editor"), + tr("OpenStoryTeller node editor." + "Build your own stories on an open source hardware.")); } void ToolBar::AddDockToMenu(QAction *action) diff --git a/story-editor/src/toolbar.h b/story-editor/src/toolbar.h index 2c4afb4..d312a91 100644 --- a/story-editor/src/toolbar.h +++ b/story-editor/src/toolbar.h @@ -12,9 +12,8 @@ public: ToolBar(); void createActions(QMenuBar *menuBar); void AddDockToMenu(QAction *action); - void SetAllDocks(bool enable); void SetActionsActive(bool enable); - void ShowAllDocks(bool enable); + void GenerateRecentProjectsMenu(const QStringList &recents); signals: void sigNew(); @@ -22,12 +21,15 @@ signals: void sigOpen(); void sigClose(); void sigExit(); - void sigAbout(); void sigDefaultDocksPosition(); + void sigOpenRecent(const QString &project); + +private slots: + void slotAbout(); private: QMenu *m_windowsMenu; - QAction *m_closeAllDocksAction; + QMenu *m_recentProjectsMenu; QAction *m_saveProjectAction; QAction *m_closeProjectAction; QList m_actionDockList; diff --git a/story-editor/src/vm_dock.cpp b/story-editor/src/vm_dock.cpp index d6a98ec..7092369 100644 --- a/story-editor/src/vm_dock.cpp +++ b/story-editor/src/vm_dock.cpp @@ -1,7 +1,7 @@ #include "vm_dock.h" VmDock::VmDock(Chip32::Assembler &assembler) - : DockWidgetBase(tr("Virtual Machine")) + : DockWidgetBase(tr("Virtual Machine"), false) { setObjectName("VirtualMachineDock"); // used to save the state