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
102 lines
3.3 KiB
C++
102 lines
3.3 KiB
C++
#include "ModalDialog.h"
|
|
|
|
#include <QBoxLayout>
|
|
#include <QChar>
|
|
#include <QFont>
|
|
#include <QHBoxLayout>
|
|
#include <QKeyEvent>
|
|
#include <QLabel>
|
|
#include <QPushButton>
|
|
#include <QString>
|
|
|
|
namespace
|
|
{
|
|
// The header's own metrics, matching the row the blueprint selection dialog drew
|
|
// before this class existed (REQ-UI-BLUEPRINT-DIALOG).
|
|
const int kSpacingPx = 8;
|
|
const int kSmallButtonSizePx = 22;
|
|
const QChar kCrossGlyph(0x00D7); // U+00D7 MULTIPLICATION SIGN
|
|
|
|
// Q dismisses an open dialog, beside the Escape that QDialog already handles
|
|
// (REQ-UI-DIALOG-DISMISS). Ctrl must not be held, matching how the game world's
|
|
// table separates a chord from the bare key (resolveKeyAction in
|
|
// lib/core/ControlAction.cpp). This deliberately stays out of that table: the table
|
|
// answers what an input does in the player's current situation, and a dialog has no
|
|
// situation -- it holds focus and takes the key whatever the world beneath it is
|
|
// doing.
|
|
bool isDismissKey(const QKeyEvent& event)
|
|
{
|
|
return event.key() == Qt::Key_Q
|
|
&& (event.modifiers() & Qt::ControlModifier) == 0;
|
|
}
|
|
}
|
|
|
|
ModalDialog::ModalDialog(QWidget* parent)
|
|
: QDialog(parent)
|
|
{
|
|
// An ordinary child widget rather than a window: the layer places it and the dim
|
|
// behind it is the same widget's paint (REQ-UI-MODAL-CHROME). Square corners rather
|
|
// than rounded ones -- rounding needs a translucent background, which is unreliable
|
|
// on Windows. The border matches the build bar and the selection panel.
|
|
setWindowFlags(Qt::Widget);
|
|
setAttribute(Qt::WA_StyledBackground, true);
|
|
// A type selector, so it reaches every subclass but none of the child widgets
|
|
// inside them, which draw themselves.
|
|
setStyleSheet(QStringLiteral(
|
|
"ModalDialog { background-color: palette(window);"
|
|
" border: 1px solid palette(mid); }"));
|
|
}
|
|
|
|
bool ModalDialog::isDismissible() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
QWidget* ModalDialog::getInitialFocusWidget() const
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
void ModalDialog::requestDismiss()
|
|
{
|
|
reject();
|
|
}
|
|
|
|
QHBoxLayout* ModalDialog::addHeader(QBoxLayout* mainLayout, const QString& title,
|
|
bool withCloseButton)
|
|
{
|
|
QHBoxLayout* headerLayout = new QHBoxLayout();
|
|
headerLayout->setSpacing(kSpacingPx);
|
|
|
|
QLabel* titleLabel = new QLabel(title, this);
|
|
QFont headerFont = titleLabel->font();
|
|
headerFont.setBold(true);
|
|
titleLabel->setFont(headerFont);
|
|
headerLayout->addWidget(titleLabel);
|
|
|
|
// The stretch is added before the close button so a subclass inserting after the
|
|
// title lands left of it, where the blueprint dialog's hotkey badge belongs.
|
|
headerLayout->addStretch();
|
|
|
|
if (withCloseButton)
|
|
{
|
|
QPushButton* closeButton = new QPushButton(QString(kCrossGlyph), this);
|
|
closeButton->setFixedSize(kSmallButtonSizePx, kSmallButtonSizePx);
|
|
connect(closeButton, &QPushButton::clicked, this, &QDialog::reject);
|
|
headerLayout->addWidget(closeButton);
|
|
}
|
|
|
|
mainLayout->insertLayout(0, headerLayout);
|
|
return headerLayout;
|
|
}
|
|
|
|
void ModalDialog::keyPressEvent(QKeyEvent* event)
|
|
{
|
|
if (isDismissible() && isDismissKey(*event))
|
|
{
|
|
requestDismiss();
|
|
return;
|
|
}
|
|
QDialog::keyPressEvent(event);
|
|
}
|