dismiss a dialog with Q, and refuse to dismiss the drop dialog

Three dialogs take Q as a second way out beside Escape: the recipe/schematic
selection dialog and the blueprint selection dialog close outright, and the
layout configuration dialog steps out one level per press -- the module being
placed, then remove mode, then the session. Both of those mode exits now go
through the handler the Remove button uses, extracted from a lambda into
onRemoveButtonClicked(), so the key and the button cannot leave different
state behind.

The schematic choice dialog goes the other way and declines reject(). It had
no close button but Escape still closed it, and the caller then applied
choiceIndex 0 -- awarding whichever option happened to be first. Refusing
reject() covers Escape, Alt+F4, and the window manager together, since all
three funnel through it. It is also the only dialog whose dismissal would
strand state: the poll that opened it does not reopen it while the choices
stay pending, so a drop dismissed is a drop lost.

The key itself is spelled once in DialogDismiss.h rather than in three key
handlers. It stays out of the ControlAction table on purpose: that table
answers what an input does in the player's current situation, and a dialog
has none -- it holds focus and takes the key whatever the world is doing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ne3mejABZoLWKLh8fgpM3x
This commit is contained in:
2026-08-17 22:31:59 +02:00
parent 1e24b87640
commit 5968e5f40a
11 changed files with 115 additions and 21 deletions

View File

@@ -4,6 +4,7 @@
#include <cmath>
#include <functional>
#include "DialogDismiss.h"
#include "DisplayName.h"
#include "OptionButton.h"
#include "ProductionRules.h"
@@ -575,24 +576,8 @@ ShipLayoutDialog::ShipLayoutDialog(const GameConfig* config,
++row;
}
buttonGrid->addWidget(m_removeButton, row, 0, 1, kCols);
connect(m_removeButton, &QPushButton::clicked, this, [this]() {
if (m_removeMode)
{
m_removeMode = false;
m_removeButton->setChecked(false);
}
else
{
for (QPushButton* btn : m_moduleButtons)
{
if (btn) { btn->setChecked(false); }
}
m_activeModuleIndex = std::nullopt;
m_removeMode = true;
m_removeButton->setChecked(true);
}
updateGridWidget();
});
connect(m_removeButton, &QPushButton::clicked,
this, &ShipLayoutDialog::onRemoveButtonClicked);
centerLayout->addLayout(buttonGrid);
centerLayout->addStretch();
@@ -697,6 +682,16 @@ void ShipLayoutDialog::keyPressEvent(QKeyEvent* event)
}
updateGridWidget();
}
else if (isDialogDismissKey(*event))
{
// Q backs out one level per press, as it does in the game world: the module
// being placed, then remove mode, then the dialog itself, which discards the
// session (REQ-UI-DIALOG-DISMISS). Both mode exits go through the handler the
// button uses, so the key and the button can never leave different state behind.
if (m_activeModuleIndex.has_value()) { onModuleButtonClicked(*m_activeModuleIndex); }
else if (m_removeMode) { onRemoveButtonClicked(); }
else { onCancel(); }
}
else
{
QDialog::keyPressEvent(event);
@@ -723,6 +718,26 @@ void ShipLayoutDialog::onModuleButtonClicked(int index)
updateGridWidget();
}
void ShipLayoutDialog::onRemoveButtonClicked()
{
if (m_removeMode)
{
m_removeMode = false;
m_removeButton->setChecked(false);
}
else
{
for (QPushButton* btn : m_moduleButtons)
{
if (btn) { btn->setChecked(false); }
}
m_activeModuleIndex = std::nullopt;
m_removeMode = true;
m_removeButton->setChecked(true);
}
updateGridWidget();
}
void ShipLayoutDialog::onConfirm()
{
ShipLayoutConfig layout;