put the caret in the name prompt's line edit

The layer asked the modal's focusWidget() what to focus, but hosting a
modal reparents it into a scroll area and a reparent clears the focus its
constructor set, so the answer was always nothing and the dialog itself
took the keyboard. Modals now name their initial focus widget outright,
which survives the hosting.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ne3mejABZoLWKLh8fgpM3x
This commit is contained in:
2026-08-18 22:05:28 +02:00
parent 1ffacae2fb
commit b7fe5cfcf2
5 changed files with 26 additions and 8 deletions

View File

@@ -52,6 +52,11 @@ bool ModalDialog::isDismissible() const
return false;
}
QWidget* ModalDialog::getInitialFocusWidget() const
{
return nullptr;
}
void ModalDialog::requestDismiss()
{
reject();

View File

@@ -30,6 +30,12 @@ public:
// has no way out but choosing (REQ-DEF-SCHEMATIC-DROP).
virtual bool isDismissible() const;
// The widget that should hold the keyboard once the modal is open -- a name prompt's
// line edit -- or nullptr to leave the focus on the modal itself. Asked for by
// ModalLayer rather than read off focusWidget(), because hosting the modal reparents
// it and a reparent clears the focus its constructor set.
virtual QWidget* getInitialFocusWidget() const;
public slots:
// What a dismissal does, whichever gesture asked for it. Cancelling is the default;
// a dialog with modes of its own overrides this to back out of them one at a time

View File

@@ -66,17 +66,15 @@ int ModalLayer::execute(ModalDialog& content, const QRect& anchorRect)
content.show();
updateVisibility();
// The modal's own focus widget where it has one -- a name dialog puts the caret in
// The widget the modal names where it names one -- a name dialog puts the caret in
// its line edit -- and the modal itself otherwise, so keys reach it and not the game
// world behind (REQ-UI-MODAL-CHROME).
if (content.focusWidget() != nullptr)
QWidget* focusTarget = content.getInitialFocusWidget();
if (focusTarget == nullptr)
{
content.focusWidget()->setFocus();
}
else
{
content.setFocus();
focusTarget = &content;
}
focusTarget->setFocus();
// The dialog's own loop, run here rather than by QDialog::exec(), so the layer knows
// what is open and can place it, dim behind it, and take the clicks beside it.

View File

@@ -46,10 +46,17 @@ NameInputDialog::NameInputDialog(const QString& title, const QString& prompt,
addHeader(mainLayout, title, false);
// The line edit is what the player came here to use, and it takes Q as a character
// because it holds the focus (REQ-UI-DIALOG-DISMISS).
// because it holds the focus (REQ-UI-DIALOG-DISMISS). Set here for a dialog shown on
// its own, and named again through getInitialFocusWidget() for the usual case: being
// hosted on the modal layer reparents this dialog, which clears the focus set here.
m_nameEdit->setFocus();
}
QWidget* NameInputDialog::getInitialFocusWidget() const
{
return m_nameEdit;
}
QString NameInputDialog::getName() const
{
return m_nameEdit->text().trimmed();

View File

@@ -25,6 +25,8 @@ public:
QString getName() const;
QWidget* getInitialFocusWidget() const override;
private:
QLineEdit* m_nameEdit;
};