diff --git a/docs/requirements.md b/docs/requirements.md index 39ce94f..9875317 100644 --- a/docs/requirements.md +++ b/docs/requirements.md @@ -471,7 +471,7 @@ The screen is divided into two columns: a main column (75% width) containing the ### Blueprint Panel -- REQ-UI-BLUEPRINT-PANEL: The blueprint panel is shown to the right of the build button grid. It contains, from top to bottom: a "Create Blueprint" button, and a list of blueprint entries (one per saved blueprint, in creation order). +- REQ-UI-BLUEPRINT-PANEL: The blueprint panel is shown to the right of the build button grid. It contains, from top to bottom: a "Create Blueprint" button, and a list of blueprint entries (one per saved blueprint, in creation order). The panel has no Save or Load buttons; blueprints are persisted automatically (REQ-UI-BLUEPRINT-SAVE) and restored at startup (REQ-UI-BLUEPRINT-LOAD). - REQ-UI-BLUEPRINT-CREATE: The "Create Blueprint" button is enabled only when at least one player-placeable building (i.e. a building with a button in the build button grid) is currently selected; non-player-placeable buildings (HQ, defence stations) in the selection do not count toward this condition. When clicked, a modal dialog appears prompting the player to enter a name. The dialog has Confirm and Cancel buttons. Clicking Cancel closes the dialog with no effect. Clicking Confirm with a non-empty name creates a blueprint from the current selection, silently excluding any non-player-placeable buildings, and appends its button to the blueprint list. @@ -485,9 +485,9 @@ The screen is divided into two columns: a main column (75% width) containing the - REQ-UI-BLUEPRINT-DELETE: Clicking the delete icon ("×") on a blueprint entry immediately removes that blueprint from the list. If the deleted blueprint was active in blueprint placement mode, that mode is exited. -- REQ-UI-BLUEPRINT-SAVE: A "Save" button is shown at the bottom of the blueprint panel. Clicking it serializes all current blueprints to a file named `blueprints.toml` located in the same directory as the application executable. The TOML structure matches REQ-UI-BLUEPRINT-STORAGE. If writing fails, a modal error dialog is shown describing the failure. +- REQ-UI-BLUEPRINT-SAVE: On application shutdown, all current blueprints are serialized to a file named `blueprints.toml` located in the same directory as the application executable. The TOML structure matches REQ-UI-BLUEPRINT-STORAGE. Write errors are silently ignored on shutdown (no button, no dialog). -- REQ-UI-BLUEPRINT-LOAD: A "Load" button is shown at the bottom of the blueprint panel, to the right of the "Save" button. Clicking it shows a confirmation dialog ("Load blueprints? This will replace all current blueprints.") with Confirm and Cancel buttons. Clicking Cancel closes the dialog with no effect. Clicking Confirm reads `blueprints.toml` from the same directory as the application executable, replaces all current blueprints with those from the file (in the order they appear in the file), and exits any active blueprint-related mode (blueprint placement mode, delete mode). If the file does not exist or cannot be parsed, a modal error dialog is shown describing the failure and the current blueprint list is left unchanged. +- REQ-UI-BLUEPRINT-LOAD: At application startup, blueprints are loaded from `blueprints.toml` in the same directory as the application executable, populating the blueprint list (in the order they appear in the file). If the file does not exist, the blueprint list starts empty with no error. If the file exists but cannot be parsed (malformed TOML), a modal error dialog describes the failure and the blueprint list starts empty. There is no Load button and no runtime reload. ## Balancing Tool diff --git a/src/ui/BlueprintPanel.cpp b/src/ui/BlueprintPanel.cpp index 1b5f2bd..8b57cea 100644 --- a/src/ui/BlueprintPanel.cpp +++ b/src/ui/BlueprintPanel.cpp @@ -53,23 +53,15 @@ BlueprintPanel::BlueprintPanel(Simulation* sim, const GameConfig* config, QWidge connect(m_createBtn, &QPushButton::clicked, this, &BlueprintPanel::onCreateClicked); - QHBoxLayout* ioLayout = new QHBoxLayout(); - m_saveBtn = new QPushButton(tr("Save"), this); - m_loadBtn = new QPushButton(tr("Load"), this); - m_saveBtn->setFixedHeight(36); - m_loadBtn->setFixedHeight(36); - ioLayout->addWidget(m_saveBtn); - ioLayout->addWidget(m_loadBtn); - layout->addLayout(ioLayout); - - connect(m_saveBtn, &QPushButton::clicked, this, &BlueprintPanel::onSaveClicked); - connect(m_loadBtn, &QPushButton::clicked, this, &BlueprintPanel::onLoadClicked); + loadFromDisk(); + rebuildButtons(); registerForEvents(); } BlueprintPanel::~BlueprintPanel() { + saveToDisk(); unregisterForEvents(); } @@ -281,65 +273,35 @@ void BlueprintPanel::rebuildButtons() refreshButtonStates(); } -void BlueprintPanel::onSaveClicked() +void BlueprintPanel::saveToDisk() const { + // Persist on shutdown; write errors are silently ignored (REQ-UI-BLUEPRINT-SAVE). const QString path = QCoreApplication::applicationDirPath() + "/blueprints.toml"; + QFile file(path); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { return; } try { const std::string content = BlueprintSerializer::serialize(m_blueprints); - QFile file(path); - if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) - { - QMessageBox::critical(this, tr("Save Failed"), - tr("Could not open file for writing:\n%1").arg(path)); - return; - } file.write(QByteArray::fromStdString(content)); } - catch (const std::exception& e) - { - QMessageBox::critical(this, tr("Save Failed"), - tr("Failed to save blueprints:\n%1").arg(e.what())); - } + catch (...) {} } -void BlueprintPanel::onLoadClicked() +void BlueprintPanel::loadFromDisk() { - QMessageBox box(this); - box.setWindowTitle(tr("Load Blueprints")); - box.setText(tr("Load blueprints? This will replace all current blueprints.")); - QPushButton* confirmBtn = box.addButton(tr("Confirm"), QMessageBox::AcceptRole); - box.addButton(tr("Cancel"), QMessageBox::RejectRole); - box.exec(); - if (box.clickedButton() != confirmBtn) { return; } - + // Load at startup (REQ-UI-BLUEPRINT-LOAD). Missing file: start empty, no error. const QString path = QCoreApplication::applicationDirPath() + "/blueprints.toml"; + QFile file(path); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { return; } try { - QFile file(path); - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) - { - QMessageBox::critical(this, tr("Load Failed"), - tr("Could not open file:\n%1").arg(path)); - return; - } - const std::string content = file.readAll().toStdString(); - std::vector loaded = BlueprintSerializer::deserialize(content); - - if (m_activeIndex >= 0) - { - EventManager::getInstance()->sendEventImmediately( - std::make_shared()); - m_activeIndex = -1; - } - - m_blueprints = std::move(loaded); - rebuildButtons(); + m_blueprints = BlueprintSerializer::deserialize(file.readAll().toStdString()); } catch (const std::exception& e) { QMessageBox::critical(this, tr("Load Failed"), tr("Failed to load blueprints:\n%1").arg(e.what())); + m_blueprints.clear(); } } diff --git a/src/ui/BlueprintPanel.h b/src/ui/BlueprintPanel.h index 3552c11..bf16846 100644 --- a/src/ui/BlueprintPanel.h +++ b/src/ui/BlueprintPanel.h @@ -38,8 +38,6 @@ private slots: void onCreateClicked(); void onDeleteBlueprintClicked(int index); void onBlueprintButtonClicked(int index); - void onSaveClicked(); - void onLoadClicked(); private: void onSelectionChanged(const std::vector& ids); @@ -48,6 +46,8 @@ private: int computeBlueprintCost(const Blueprint& bp) const; void rebuildButtons(); void refreshButtonStates(); + void loadFromDisk(); + void saveToDisk() const; Simulation* m_sim; const GameConfig* m_config; @@ -57,8 +57,6 @@ private: std::vector m_blueprints; std::vector m_blueprintButtons; QPushButton* m_createBtn; - QPushButton* m_saveBtn; - QPushButton* m_loadBtn; QWidget* m_buttonsContainer; QVBoxLayout* m_buttonsLayout; };