2026-07-30 12:06:02 +08:00
# include "MainWindow.h"
# include <array>
2026-09-17 15:30:30 +08:00
# include <QAbstractItemView>
2026-07-30 12:06:02 +08:00
# include <QComboBox>
2026-09-17 15:30:30 +08:00
# include <QDateTime>
# include <QGridLayout>
# include <QHBoxLayout>
# include <QJsonArray>
# include <QJsonObject>
# include <QJsonValue>
2026-07-30 12:06:02 +08:00
# include <QLabel>
2026-09-17 15:30:30 +08:00
# include <QLineEdit>
# include <QMessageBox>
2026-07-30 12:06:02 +08:00
# include <QPushButton>
2026-09-17 15:30:30 +08:00
# include <QRegularExpression>
# include <QRegularExpressionValidator>
2026-07-30 12:06:02 +08:00
# include <QResizeEvent>
2026-09-17 15:30:30 +08:00
# include <QScroller>
# include <QScrollerProperties>
2026-07-30 12:06:02 +08:00
# include <QSignalBlocker>
# include <QStatusBar>
2026-09-17 15:30:30 +08:00
# include <QTimer>
# include <QVariant>
# include <QVBoxLayout>
2026-07-30 12:06:02 +08:00
# include "RemoteGuideClient.h"
2026-09-17 15:30:30 +08:00
# include "dialogcontrolpassword.h"
2026-07-30 12:06:02 +08:00
# include "IVrUtils.h"
# include "Version.h"
# include "ui_MainWindow.h"
namespace {
2026-09-17 15:30:30 +08:00
void EnableComboBoxTouchScroll ( QComboBox * combo )
{
if ( ! combo ) {
return ;
}
combo - > setMaxVisibleItems ( 8 ) ;
QAbstractItemView * view = combo - > view ( ) ;
if ( ! view ) {
return ;
}
view - > setVerticalScrollBarPolicy ( Qt : : ScrollBarAlwaysOn ) ;
view - > setHorizontalScrollBarPolicy ( Qt : : ScrollBarAlwaysOff ) ;
view - > setVerticalScrollMode ( QAbstractItemView : : ScrollPerPixel ) ;
view - > setTextElideMode ( Qt : : ElideNone ) ;
QWidget * viewport = view - > viewport ( ) ;
viewport - > setAttribute ( Qt : : WA_AcceptTouchEvents , true ) ;
QScroller : : grabGesture ( viewport , QScroller : : LeftMouseButtonGesture ) ;
QScroller * scroller = QScroller : : scroller ( viewport ) ;
if ( scroller ) {
QScrollerProperties properties = scroller - > scrollerProperties ( ) ;
properties . setScrollMetric (
QScrollerProperties : : VerticalOvershootPolicy ,
QVariant ( static_cast < int > ( QScrollerProperties : : OvershootAlwaysOff ) ) ) ;
scroller - > setScrollerProperties ( properties ) ;
}
}
2026-07-30 12:06:02 +08:00
QString NonEmpty ( const QString & value , const QString & fallback )
{
const QString trimmed = value . trimmed ( ) ;
return trimmed . isEmpty ( ) ? fallback : trimmed ;
}
bool IsKnownAircraftModel ( const QString & modelType )
{
const QString normalized = modelType . trimmed ( ) . toUpper ( ) ;
2026-09-17 15:30:30 +08:00
return ! normalized . isEmpty ( ) & &
normalized ! = QStringLiteral ( " 未知 " ) & &
normalized ! = QStringLiteral ( " ---- " ) ;
2026-07-30 12:06:02 +08:00
}
QString FormatModelVerification ( const QString & expectedModel ,
const QString & detectedModel ,
bool modelMatched )
{
const QString expected = expectedModel . trimmed ( ) . toUpper ( ) ;
const QString detected = detectedModel . trimmed ( ) . toUpper ( ) ;
if ( ! IsKnownAircraftModel ( detected ) ) {
return QStringLiteral ( " 未知 " ) ;
}
if ( ! IsKnownAircraftModel ( expected ) ) {
return QStringLiteral ( " 实测(%1) " ).arg(detected) ;
}
if ( modelMatched ) {
return QStringLiteral ( " 匹配(%1) " ).arg(detected) ;
}
if ( detected . compare ( expected , Qt : : CaseInsensitive ) ! = 0 ) {
return QStringLiteral ( " 不匹配(%1) " ).arg(detected) ;
}
return QStringLiteral ( " 未知 " ) ;
}
2026-09-17 15:30:30 +08:00
void ApplyActivatedLabel ( QLabel * label , bool activated )
{
if ( ! label ) {
return ;
}
label - > setText ( activated
? QStringLiteral ( " 系统已激活 " )
: QStringLiteral ( " 未激活 " ) ) ;
label - > setStyleSheet ( activated
? QStringLiteral ( " color: rgb(0, 255, 0); font-size: 26px; font-weight: 700; " )
: QStringLiteral ( " color: rgb(172, 181, 194); font-size: 26px; font-weight: 700; " ) ) ;
}
QJsonObject RuntimeStatusObject ( const QJsonObject & root )
{
const QJsonValue value = root . value ( QStringLiteral ( " status " ) ) ;
return value . isObject ( ) ? value . toObject ( ) : QJsonObject ( ) ;
}
bool ActivatedOf ( const QJsonObject & root )
{
const QJsonObject status = RuntimeStatusObject ( root ) ;
if ( status . contains ( QStringLiteral ( " activated " ) ) ) {
return status . value ( " activated " ) . toBool ( ) ;
}
return root . value ( " activated " ) . toBool ( ) ;
}
QString WorkModeOf ( const QJsonObject & root )
{
const QJsonObject status = RuntimeStatusObject ( root ) ;
if ( status . contains ( QStringLiteral ( " workMode " ) ) ) {
return status . value ( " workMode " ) . toString ( ) ;
}
return root . value ( " workMode " ) . toString ( ) ;
}
QString FormatProtocolTime ( const QJsonValue & value )
{
if ( value . isString ( ) ) {
const QString text = value . toString ( ) . trimmed ( ) ;
return text . isEmpty ( ) ? QStringLiteral ( " -- " ) : text ;
}
const qint64 epoch = value . toVariant ( ) . toLongLong ( ) ;
if ( epoch < = 0 ) {
return QStringLiteral ( " -- " ) ;
}
return QDateTime : : fromMSecsSinceEpoch ( epoch ) . toString (
QStringLiteral ( " yyyy-MM-dd HH:mm:ss " ) ) ;
}
2026-07-30 12:06:02 +08:00
} // namespace
MainWindow : : MainWindow ( int discoveryPort , QWidget * parent )
: QMainWindow ( parent )
, ui ( new Ui : : MainWindow )
{
ui - > setupUi ( this ) ;
2026-09-17 15:30:30 +08:00
EnableComboBoxTouchScroll ( ui - > modelCombo ) ;
2026-07-30 12:06:02 +08:00
setWindowTitle ( QStringLiteral ( " 停机引导远程显示 %1 " )
. arg ( QString : : fromLatin1 (
PARKINGSPACEGUIDEVIEW_FULL_VERSION_STRING ) ) ) ;
const QString versionWithBuildTime = QStringLiteral ( " %1_%2%3%4%5%6%7 " )
. arg ( QString : : fromLatin1 (
PARKINGSPACEGUIDEVIEW_FULL_VERSION_STRING ) )
. arg ( YEAR )
. arg ( MONTH , 2 , 10 , QChar ( ' 0 ' ) )
. arg ( DAY , 2 , 10 , QChar ( ' 0 ' ) )
. arg ( HOUR , 2 , 10 , QChar ( ' 0 ' ) )
. arg ( MINUTE , 2 , 10 , QChar ( ' 0 ' ) )
. arg ( SECOND , 2 , 10 , QChar ( ' 0 ' ) ) ;
ui - > versionLabel - > setText ( versionWithBuildTime ) ;
statusBar ( ) - > addPermanentWidget ( ui - > versionLabel ) ;
connect ( ui - > windowButton , & QPushButton : : clicked ,
this , & QWidget : : showMinimized ) ;
connect ( ui - > closeWindowButton , & QPushButton : : clicked ,
this , & QWidget : : close ) ;
m_client = new RemoteGuideClient ( discoveryPort , this ) ;
connect ( m_client , & RemoteGuideClient : : ConnectionStateChanged ,
this , & MainWindow : : HandleConnectionState ) ;
connect ( m_client , & RemoteGuideClient : : StatusReceived ,
this , & MainWindow : : ApplyStatus ) ;
connect ( m_client , & RemoteGuideClient : : GuidanceRunningChanged ,
this , [ this ] ( bool running ) {
const bool wasRunning = m_guidanceRunning ;
m_guidanceRunning = running ;
if ( wasRunning & & ! running ) {
const QSignalBlocker blocker ( ui - > modelCombo ) ;
ui - > modelCombo - > setCurrentIndex ( 0 ) ;
}
UpdateControls ( ) ;
} ) ;
connect ( m_client , & RemoteGuideClient : : CommandFinished ,
this , & MainWindow : : HandleCommandFinished ) ;
connect ( ui - > modelCombo ,
QOverload < int > : : of ( & QComboBox : : currentIndexChanged ) ,
this ,
[ this ] ( int index ) {
if ( index > 0 & & m_client & & m_client - > IsConnected ( ) ) {
m_commandPending = true ;
UpdateControls ( ) ;
m_client - > SelectModel ( ui - > modelCombo - > currentText ( ) ) ;
}
UpdateControls ( ) ;
} ) ;
connect ( ui - > startButton , & QPushButton : : clicked , this , [ this ] ( ) {
2026-09-17 15:30:30 +08:00
const QString password = PromptControlPassword ( QStringLiteral ( " 开始引导 " ) ) ;
if ( m_requireLogin & & password . isEmpty ( ) ) {
return ;
}
2026-07-30 12:06:02 +08:00
m_commandPending = true ;
UpdateControls ( ) ;
m_client - > StartGuidance (
ui - > modelCombo - > currentIndex ( ) > 0
? ui - > modelCombo - > currentText ( )
2026-09-17 15:30:30 +08:00
: QString ( ) ,
password ) ;
2026-07-30 12:06:02 +08:00
} ) ;
connect ( ui - > stopButton , & QPushButton : : clicked , this , [ this ] ( ) {
2026-09-17 15:30:30 +08:00
const QString password = PromptControlPassword ( QStringLiteral ( " 停止引导 " ) ) ;
if ( m_requireLogin & & password . isEmpty ( ) ) {
return ;
}
2026-07-30 12:06:02 +08:00
m_commandPending = true ;
UpdateControls ( ) ;
2026-09-17 15:30:30 +08:00
m_client - > StopGuidance ( password ) ;
} ) ;
AddStatusRow ( QStringLiteral ( " 系统状态 " ) , m_activatedLabel ) ;
AddStatusRow ( QStringLiteral ( " 自检 " ) , m_selfCheckLabel ) ;
AddStatusRow ( QStringLiteral ( " 工作模式 " ) , m_workModeLabel ) ;
AddStatusRow ( QStringLiteral ( " 人员轮挡 " ) , m_personChockLabel ) ;
AddStatusRow ( QStringLiteral ( " 上轮挡 " ) , m_chockOnLabel ) ;
AddStatusRow ( QStringLiteral ( " 下轮挡 " ) , m_chockOffLabel ) ;
auto * passwordLayout = new QHBoxLayout ( ) ;
m_passwordEdit = new QLineEdit ( ui - > sidePanel ) ;
m_passwordEdit - > setEchoMode ( QLineEdit : : Password ) ;
m_passwordEdit - > setMaxLength ( DialogControlPassword : : kPasswordLength ) ;
m_passwordEdit - > setValidator ( new QRegularExpressionValidator (
QRegularExpression ( QStringLiteral ( " ^[0-9]{0,6}$ " ) ) ,
m_passwordEdit ) ) ;
m_passwordEdit - > setInputMethodHints ( Qt : : ImhDigitsOnly ) ;
m_passwordEdit - > setPlaceholderText ( QStringLiteral ( " 6位数字密码 " ) ) ;
m_passwordEdit - > setStyleSheet (
QStringLiteral ( " color: rgb(239, 241, 245); background-color: rgb(37, 38, 42); "
" border: 1px solid rgb(92, 95, 104); padding-left: 12px; "
" font-size: 18px; min-height: 42px; " ) ) ;
m_loginButton = new QPushButton ( QStringLiteral ( " 登录 " ) , ui - > sidePanel ) ;
m_loginButton - > setMinimumHeight ( 42 ) ;
passwordLayout - > addWidget ( m_passwordEdit , 1 ) ;
passwordLayout - > addWidget ( m_loginButton ) ;
ui - > sideLayout - > insertLayout ( ui - > sideLayout - > count ( ) - 1 , passwordLayout ) ;
auto * actionLayout = new QHBoxLayout ( ) ;
m_maintenanceButton = new QPushButton ( QStringLiteral ( " 维护模式 " ) , ui - > sidePanel ) ;
actionLayout - > addWidget ( m_maintenanceButton ) ;
ui - > sideLayout - > insertLayout ( ui - > sideLayout - > count ( ) - 1 , actionLayout ) ;
const QString chockStyle =
QStringLiteral ( " QPushButton { color: rgb(239, 241, 245); border: none; "
" border-radius: 4px; font-size: 16px; } "
" QPushButton:disabled { background-color: rgb(65, 67, 73); "
" color: rgb(130, 132, 138); } " ) ;
m_chockOnButton = new QPushButton ( QStringLiteral ( " 上轮挡 " ) , ui - > header ) ;
m_chockOnButton - > setGeometry ( 1525 , 112 , 80 , 80 ) ;
m_chockOnButton - > setToolTip ( QStringLiteral ( " 上轮挡 " ) ) ;
m_chockOnButton - > setStyleSheet (
chockStyle + QStringLiteral ( " QPushButton { background-color: rgb(60, 125, 80); } " ) ) ;
m_chockOffButton = new QPushButton ( QStringLiteral ( " 下轮挡 " ) , ui - > header ) ;
m_chockOffButton - > setGeometry ( 1645 , 112 , 80 , 80 ) ;
m_chockOffButton - > setToolTip ( QStringLiteral ( " 下轮挡 " ) ) ;
m_chockOffButton - > setStyleSheet (
chockStyle + QStringLiteral ( " QPushButton { background-color: rgb(70, 110, 160); } " ) ) ;
m_chockOnButton - > show ( ) ;
m_chockOffButton - > show ( ) ;
auto * replayLayout = new QHBoxLayout ( ) ;
m_sessionCombo = new QComboBox ( ui - > sidePanel ) ;
m_sessionCombo - > addItem ( QStringLiteral ( " 选择历史会话 " ) ) ;
m_sessionCombo - > setStyleSheet (
QStringLiteral ( " color: rgb(239, 241, 245); background-color: rgb(37, 38, 42); "
" border: 1px solid rgb(92, 95, 104); padding-left: 12px; "
" font-size: 16px; min-height: 42px; " ) ) ;
m_replayButton = new QPushButton ( QStringLiteral ( " 回放 " ) , ui - > sidePanel ) ;
m_replayButton - > setMinimumHeight ( 42 ) ;
replayLayout - > addWidget ( m_sessionCombo , 1 ) ;
replayLayout - > addWidget ( m_replayButton ) ;
ui - > sideLayout - > insertLayout ( ui - > sideLayout - > count ( ) - 1 , replayLayout ) ;
m_replayTimer = new QTimer ( this ) ;
m_replayTimer - > setSingleShot ( true ) ;
connect ( m_replayTimer , & QTimer : : timeout ,
this , & MainWindow : : PlayNextReplaySample ) ;
m_passwordExpireTimer = new QTimer ( this ) ;
m_passwordExpireTimer - > setSingleShot ( true ) ;
connect ( m_passwordExpireTimer , & QTimer : : timeout , this , [ this ] ( ) {
ExpirePasswordSession ( ) ;
} ) ;
connect ( m_loginButton , & QPushButton : : clicked , this , [ this ] ( ) {
if ( ! m_client | | ! m_client - > IsConnected ( ) ) {
return ;
}
m_commandPending = true ;
UpdateControls ( ) ;
m_client - > Login ( m_passwordEdit - > text ( ) ) ;
} ) ;
connect ( m_maintenanceButton , & QPushButton : : clicked , this , [ this ] ( ) {
if ( m_guidanceRunning ) {
return ;
}
const QString password = PromptControlPassword (
m_maintenanceMode
? QStringLiteral ( " 退出维护模式 " )
: QStringLiteral ( " 进入维护模式 " ) ) ;
if ( m_requireLogin & & password . isEmpty ( ) ) {
return ;
}
m_commandPending = true ;
UpdateControls ( ) ;
m_client - > SetMaintenanceMode ( ! m_maintenanceMode , password ) ;
} ) ;
connect ( m_chockOnButton , & QPushButton : : clicked , this , [ this ] ( ) {
RecordChockWithPassword ( true ) ;
} ) ;
connect ( m_chockOffButton , & QPushButton : : clicked , this , [ this ] ( ) {
RecordChockWithPassword ( false ) ;
} ) ;
connect ( m_replayButton , & QPushButton : : clicked , this , [ this ] ( ) {
if ( m_replaying ) {
StopReplay ( ) ;
UpdateControls ( ) ;
return ;
}
const int index = m_sessionCombo - > currentIndex ( ) ;
const QString sessionUuid =
m_sessionCombo - > itemData ( index ) . toString ( ) ;
if ( sessionUuid . isEmpty ( ) ) {
return ;
}
const QString password = PromptControlPassword ( QStringLiteral ( " 回放 " ) ) ;
if ( m_requireLogin & & password . isEmpty ( ) ) {
return ;
}
m_commandPending = true ;
UpdateControls ( ) ;
m_client - > RequestSessionSamples ( sessionUuid , password ) ;
2026-07-30 12:06:02 +08:00
} ) ;
ApplyStatus ( QJsonObject ( ) ) ;
HandleConnectionState ( false ,
QStringLiteral ( " 正在搜索停机引导设备…… " ) ,
QString ( ) ,
QString ( ) ) ;
}
MainWindow : : ~ MainWindow ( )
{
delete ui ;
}
void MainWindow : : ApplyStatus ( const QJsonObject & status )
{
2026-09-17 15:30:30 +08:00
if ( m_replaying & & m_guidanceRunning ) {
StopReplay ( ) ;
}
if ( m_replaying ) {
return ;
}
2026-07-30 12:06:02 +08:00
ui - > stateImage - > SetStatus ( status ) ;
int stateCode = status . value ( " guideStateCode " ) . toInt ( 12 ) ;
if ( stateCode < 1 | | stateCode > 24 ) {
stateCode = 12 ;
}
ApplyGuideState ( stateCode , status . value ( " guideText " ) . toString ( ) ) ;
ui - > targetIdLabel - > setText ( NonEmpty (
status . value ( " targetId " ) . toString ( ) , QStringLiteral ( " T001 " ) ) ) ;
ui - > parkIdLabel - > setText ( NonEmpty (
status . value ( " parkId " ) . toString ( ) , QStringLiteral ( " 未知 " ) ) ) ;
QString expectedModel = status . value ( " expectedModelType " )
. toString ( ) . trimmed ( ) ;
if ( m_guidanceRunning & & IsKnownAircraftModel ( expectedModel ) & &
ui - > modelCombo - > currentText ( ) ! = expectedModel ) {
const QSignalBlocker blocker ( ui - > modelCombo ) ;
ui - > modelCombo - > setCurrentText ( expectedModel ) ;
}
if ( expectedModel . isEmpty ( ) & & ui - > modelCombo - > currentIndex ( ) > 0 ) {
expectedModel = ui - > modelCombo - > currentText ( ) ;
}
const QString detectedModel = NonEmpty (
status . value ( " modelType " ) . toString ( ) , QStringLiteral ( " 未知 " ) ) ;
ui - > modelMatchLabel - > setText ( FormatModelVerification (
expectedModel ,
detectedModel ,
status . value ( " modelMatched " ) . toBool ( ) ) ) ;
2026-09-17 15:30:30 +08:00
const int personState = status . value ( " guideStateCode " ) . toInt ( 0 ) ;
const QString personChockTime =
FormatProtocolTime ( status . value ( " personChockTime " ) ) ;
ui - > personLabel - > setText (
personState = = 21 | | personChockTime ! = QStringLiteral ( " -- " )
? QStringLiteral ( " 已确认 " )
: QStringLiteral ( " -- " ) ) ;
ui - > personCountLabel - > setText ( QStringLiteral ( " -- " ) ) ;
2026-07-30 12:06:02 +08:00
SetMetric ( ui - > distanceLabel , status , " distanceToStop " ,
2026-09-19 10:01:32 +08:00
QStringLiteral ( " 毫米 " ) , 0 ) ;
2026-07-30 12:06:02 +08:00
SetMetric ( ui - > offsetLabel , status , " lateralOffset " ,
2026-09-19 10:01:32 +08:00
QStringLiteral ( " 毫米 " ) , 0 ) ;
2026-07-30 12:06:02 +08:00
SetMetric ( ui - > yawLabel , status , " bodyYawAngle " , QStringLiteral ( " 度 " ) ) ;
SetMetric ( ui - > speedLabel , status , " aircraftSpeed " , QStringLiteral ( " 米/秒 " ) ) ;
2026-09-17 15:30:30 +08:00
const bool activated = ActivatedOf ( status ) ;
ApplyActivatedLabel ( m_activatedLabel , activated ) ;
const QJsonObject selfCheck = status . value ( " selfCheck " ) . toObject ( ) ;
if ( m_selfCheckLabel ) {
if ( selfCheck . value ( " running " ) . toBool ( ) ) {
m_selfCheckLabel - > setText ( QStringLiteral ( " 自检中 " ) ) ;
} else if ( selfCheck . isEmpty ( ) ) {
m_selfCheckLabel - > setText ( QStringLiteral ( " -- " ) ) ;
} else {
m_selfCheckLabel - > setText ( NonEmpty (
selfCheck . value ( " message " ) . toString ( ) ,
selfCheck . value ( " ok " ) . toBool ( )
? QStringLiteral ( " 通过 " )
: QStringLiteral ( " 失败 " ) ) ) ;
}
}
const QString workMode = WorkModeOf ( status ) ;
m_maintenanceMode = workMode = = QStringLiteral ( " maintenance " ) ;
if ( m_workModeLabel ) {
m_workModeLabel - > setText ( m_maintenanceMode
? QStringLiteral ( " 维护模式 " )
: QStringLiteral ( " 正常工作模式 " ) ) ;
}
if ( m_personChockLabel ) {
m_personChockLabel - > setText (
FormatProtocolTime ( status . value ( " personChockTime " ) ) ) ;
}
if ( m_chockOnLabel ) {
m_chockOnLabel - > setText (
FormatProtocolTime ( status . value ( " chockOnTime " ) ) ) ;
}
if ( m_chockOffLabel ) {
m_chockOffLabel - > setText (
FormatProtocolTime ( status . value ( " chockOffTime " ) ) ) ;
}
if ( m_maintenanceButton ) {
m_maintenanceButton - > setText ( m_maintenanceMode
? QStringLiteral ( " 退出维护 " )
: QStringLiteral ( " 维护模式 " ) ) ;
}
2026-07-30 12:06:02 +08:00
}
void MainWindow : : ApplyGuideState ( int stateCode , const QString & guideText )
{
const GuidePresentation & presentation = PresentationForState ( stateCode ) ;
ui - > stateNameLabel - > setText ( presentation . name ) ;
QString stateText = QStringLiteral ( " %1 %2 " )
. arg ( stateCode )
. arg ( presentation . name ) ;
const QString trimmedGuideText = guideText . trimmed ( ) ;
if ( ! trimmedGuideText . isEmpty ( ) ) {
stateText + = QStringLiteral ( " / %1 " ) . arg ( trimmedGuideText ) ;
}
ui - > guideStateLabel - > setText ( stateText ) ;
if ( stateCode ! = m_currentStateCode ) {
m_currentStateCode = stateCode ;
ui - > stateCodeLabel - > setText (
QStringLiteral ( " 状态 #%1 " ) . arg ( stateCode , 2 , 10 , QChar ( ' 0 ' ) ) ) ;
}
}
void MainWindow : : UpdateControls ( )
{
2026-09-17 15:30:30 +08:00
const bool canOperate = m_connected & &
m_loggedIn & &
! m_commandPending ;
ui - > modelCombo - > setEnabled ( canOperate & &
2026-07-30 12:06:02 +08:00
! m_guidanceRunning & &
2026-09-17 15:30:30 +08:00
! m_replaying ) ;
ui - > startButton - > setEnabled ( canOperate & &
2026-07-30 12:06:02 +08:00
! m_guidanceRunning & &
2026-09-17 15:30:30 +08:00
! m_replaying ) ;
ui - > stopButton - > setEnabled ( canOperate & &
m_guidanceRunning ) ;
if ( m_passwordEdit ) {
m_passwordEdit - > setEnabled ( m_connected & &
m_requireLogin & &
! m_loggedIn & &
! m_commandPending ) ;
}
if ( m_loginButton ) {
m_loginButton - > setEnabled ( m_connected & &
m_requireLogin & &
! m_loggedIn & &
! m_commandPending ) ;
}
if ( m_maintenanceButton ) {
m_maintenanceButton - > setEnabled ( canOperate & & ! m_guidanceRunning ) ;
}
if ( m_chockOnButton ) {
m_chockOnButton - > setEnabled ( m_connected & & ! m_commandPending ) ;
}
if ( m_chockOffButton ) {
m_chockOffButton - > setEnabled ( m_connected & & ! m_commandPending ) ;
}
if ( m_sessionCombo ) {
m_sessionCombo - > setEnabled ( canOperate & & ! m_guidanceRunning ) ;
}
if ( m_replayButton ) {
m_replayButton - > setEnabled ( canOperate & & ! m_guidanceRunning ) ;
m_replayButton - > setText ( m_replaying
? QStringLiteral ( " 停止 " )
: QStringLiteral ( " 回放 " ) ) ;
}
2026-07-30 12:06:02 +08:00
}
void MainWindow : : SetMetric ( QLabel * label ,
const QJsonObject & status ,
const char * field ,
const QString & unit ,
int precision ,
double divisor )
{
const double safeDivisor = divisor = = 0.0 ? 1.0 : divisor ;
const double value = status . value ( QString : : fromLatin1 ( field ) )
. toDouble ( 0.0 ) / safeDivisor ;
label - > setText ( QString : : number ( value , ' f ' , precision ) + unit ) ;
}
void MainWindow : : HandleConnectionState ( bool connected ,
const QString & message ,
const QString & deviceName ,
const QString & address )
{
Q_UNUSED ( message ) ;
Q_UNUSED ( deviceName ) ;
Q_UNUSED ( address ) ;
m_connected = connected ;
m_commandPending = false ;
m_guidanceRunning = connected & & m_client
? m_client - > IsGuidanceRunning ( )
: false ;
if ( ! connected ) {
2026-09-17 15:30:30 +08:00
StopReplay ( ) ;
ExpirePasswordSession ( ) ;
m_requireLogin = true ;
2026-07-30 12:06:02 +08:00
const QSignalBlocker blocker ( ui - > modelCombo ) ;
2026-09-17 15:30:30 +08:00
ui - > modelCombo - > clear ( ) ;
ui - > modelCombo - > addItem ( QStringLiteral ( " 请选择机型 " ) ) ;
if ( m_sessionCombo ) {
const QSignalBlocker sessionBlocker ( m_sessionCombo ) ;
m_sessionCombo - > clear ( ) ;
m_sessionCombo - > addItem ( QStringLiteral ( " 选择历史会话 " ) ) ;
}
if ( m_passwordEdit ) {
m_passwordEdit - > clear ( ) ;
}
2026-07-30 12:06:02 +08:00
}
ui - > deviceLabel - > setText ( connected
? QStringLiteral ( " 已连接 " )
: QStringLiteral ( " 未连接 " ) ) ;
UpdateControls ( ) ;
}
void MainWindow : : HandleCommandFinished ( const QString & command ,
bool ok ,
const QString & message ,
const QJsonObject & response )
{
Q_UNUSED ( message ) ;
m_commandPending = false ;
if ( ok & & command = = QStringLiteral ( " start " ) ) {
m_guidanceRunning = true ;
2026-09-17 15:30:30 +08:00
StopReplay ( ) ;
2026-07-30 12:06:02 +08:00
} else if ( ok & & command = = QStringLiteral ( " stop " ) ) {
m_guidanceRunning = false ;
const QSignalBlocker blocker ( ui - > modelCombo ) ;
ui - > modelCombo - > setCurrentIndex ( 0 ) ;
} else if ( ! ok & & command = = QStringLiteral ( " select_model " ) ) {
const QSignalBlocker blocker ( ui - > modelCombo ) ;
ui - > modelCombo - > setCurrentIndex ( 0 ) ;
2026-09-17 15:30:30 +08:00
} else if ( command = = QStringLiteral ( " login " ) ) {
if ( ok ) {
BeginPasswordSession ( ) ;
m_client - > RequestSessions ( 50 ) ;
} else {
ExpirePasswordSession ( ) ;
if ( m_passwordEdit ) {
m_passwordEdit - > clear ( ) ;
}
}
} else if ( ok & & command = = QStringLiteral ( " get_panel_config " ) ) {
ApplyPanelConfig ( response . value ( " panel " ) . toObject ( ) ) ;
} else if ( ok & & command = = QStringLiteral ( " list_sessions " ) ) {
ApplySessionList ( response . value ( " sessions " ) . toArray ( ) ) ;
} else if ( ok & & command = = QStringLiteral ( " get_session_samples " ) ) {
StartReplay ( response . value ( " samples " ) . toArray ( ) ) ;
} else if ( ok & & command = = QStringLiteral ( " set_maintenance " ) ) {
if ( response . contains ( QStringLiteral ( " workMode " ) ) ) {
m_maintenanceMode = response . value ( " workMode " ) . toString ( ) = =
QStringLiteral ( " maintenance " ) ;
} else {
m_maintenanceMode = response . value ( " maintenanceMode " ) . toBool ( ) ;
}
2026-07-30 12:06:02 +08:00
}
if ( m_client & & m_client - > IsConnected ( ) ) {
m_guidanceRunning = m_client - > IsGuidanceRunning ( ) ;
}
UpdateControls ( ) ;
}
void MainWindow : : resizeEvent ( QResizeEvent * event )
{
QMainWindow : : resizeEvent ( event ) ;
}
const MainWindow : : GuidePresentation &
MainWindow : : PresentationForState ( int stateCode )
{
static const std : : array < GuidePresentation , 24 > states = { {
{ QStringLiteral ( " 泊位引导启动 " ) , QStringLiteral ( " START-OF-DOCKING " ) , QStringLiteral ( " WAIT " ) , QStringLiteral ( " 系统已进入泊位引导等待状态。 " ) , QStringLiteral ( " 等待运行箭头或接近率条出现,并确认机型正确。 " ) , QStringLiteral ( " warn " ) } ,
{ QStringLiteral ( " 捕获 " ) , QStringLiteral ( " CAPTURE " ) , QStringLiteral ( " CAPTURE " ) , QStringLiteral ( " 运行箭头和飞机类型表示系统正在搜索接近中的飞机。 " ) , QStringLiteral ( " 确认机型正确,沿引导线低速滑行。 " ) , QStringLiteral ( " good " ) } ,
{ QStringLiteral ( " 跟踪 " ) , QStringLiteral ( " TRACKING " ) , QStringLiteral ( " TRACKING " ) , QStringLiteral ( " 中心线和转向箭头提示飞机位置及修正方向。 " ) , QStringLiteral ( " 按红色箭头小幅修正方向,保持低速。 " ) , QStringLiteral ( " good " ) } ,
{ QStringLiteral ( " 接近率 " ) , QStringLiteral ( " CLOSING RATE " ) , QStringLiteral ( " CLOSING " ) , QStringLiteral ( " 数字和接近率条显示距停止位置的剩余距离。 " ) , QStringLiteral ( " 保持低速并按箭头修正,准备在 STOP 出现时停车。 " ) , QStringLiteral ( " good " ) } ,
{ QStringLiteral ( " 对准中心线 " ) , QStringLiteral ( " ALIGNED TO CENTRE " ) , QStringLiteral ( " ALIGNED " ) , QStringLiteral ( " 显示距离但无转向箭头,表示飞机已对准中心线。 " ) , QStringLiteral ( " 继续低速向停止位滑行。 " ) , QStringLiteral ( " good " ) } ,
{ QStringLiteral ( " 慢速 " ) , QStringLiteral ( " SLOW " ) , QStringLiteral ( " SLOW " ) , QStringLiteral ( " 飞机接近速度高于当前区域的允许速度。 " ) , QStringLiteral ( " 立即减速,观察是否恢复正常接近率显示。 " ) , QStringLiteral ( " warn " ) } ,
{ QStringLiteral ( " 方位引导 " ) , QStringLiteral ( " AZIMUTH GUIDANCE " ) , QStringLiteral ( " GUIDANCE " ) , QStringLiteral ( " 位置箭头和转向箭头共同提示方位修正。 " ) , QStringLiteral ( " 按转向箭头修正并保持低速。 " ) , QStringLiteral ( " good " ) } ,
{ QStringLiteral ( " 到达停止位置 " ) , QStringLiteral ( " STOP POSITION " ) , QStringLiteral ( " STOP " ) , QStringLiteral ( " 飞机已经到达正确停止位置。 " ) , QStringLiteral ( " 立即停车,等待地面确认。 " ) , QStringLiteral ( " stop " ) } ,
{ QStringLiteral ( " 泊位引导完成 " ) , QStringLiteral ( " DOCKING COMPLETED " ) , QStringLiteral ( " OK " ) , QStringLiteral ( " 泊位引导流程已经完成。 " ) , QStringLiteral ( " 保持停稳,等待地面服务。 " ) , QStringLiteral ( " good " ) } ,
{ QStringLiteral ( " 越位 " ) , QStringLiteral ( " OVERSHOOT " ) , QStringLiteral ( " TOO FAR " ) , QStringLiteral ( " 飞机已经越过目标停止位置。 " ) , QStringLiteral ( " 立即停止,等待人工处置。 " ) , QStringLiteral ( " stop " ) } ,
{ QStringLiteral ( " 提前停止 " ) , QStringLiteral ( " STOP SHORT " ) , QStringLiteral ( " STOP OK " ) , QStringLiteral ( " 飞机已静止,但尚未到达预定停止位置。 " ) , QStringLiteral ( " 等待地面确认是否继续或结束。 " ) , QStringLiteral ( " warn " ) } ,
{ QStringLiteral ( " 等待 " ) , QStringLiteral ( " WAIT " ) , QStringLiteral ( " WAIT " ) , QStringLiteral ( " 系统等待视野恢复或重新检测到飞机。 " ) , QStringLiteral ( " 保持等待,在接近率条恢复前不要继续。 " ) , QStringLiteral ( " warn " ) } ,
{ QStringLiteral ( " 异常慢速-恶劣天气 " ) , QStringLiteral ( " SLOW - WEATHER " ) , QStringLiteral ( " SLOW " ) , QStringLiteral ( " 天气原因导致系统进入异常慢速捕获模式。 " ) , QStringLiteral ( " 低速等待接近率条出现。 " ) , QStringLiteral ( " warn " ) } ,
{ QStringLiteral ( " 异常慢速-飞机丢失 " ) , QStringLiteral ( " SLOW - LOST " ) , QStringLiteral ( " SLOW " ) , QStringLiteral ( " 引导过程中暂时丢失飞机目标。 " ) , QStringLiteral ( " 低速等待系统重新检测飞机。 " ) , QStringLiteral ( " warn " ) } ,
{ QStringLiteral ( " 飞机验证失败 " ) , QStringLiteral ( " ID FAIL " ) , QStringLiteral ( " STOP ID FAIL " ) , QStringLiteral ( " 检测机型与选择机型验证失败。 " ) , QStringLiteral ( " 停止飞机,等待人工复核,不得自行继续。 " ) , QStringLiteral ( " stop " ) } ,
{ QStringLiteral ( " 机位受阻 " ) , QStringLiteral ( " GATE BLOCKED " ) , QStringLiteral ( " WAIT GATE BLOCK " ) , QStringLiteral ( " 机位前方或进近视野存在障碍。 " ) , QStringLiteral ( " 等待障碍物清除,未恢复前不要继续。 " ) , QStringLiteral ( " warn " ) } ,
{ QStringLiteral ( " 视野受阻 " ) , QStringLiteral ( " VIEW BLOCKED " ) , QStringLiteral ( " WAIT VIEW BLOCK " ) , QStringLiteral ( " 传感器视野受阻,当前无法可靠检测飞机。 " ) , QStringLiteral ( " 等待视野恢复,必要时切换人工引导。 " ) , QStringLiteral ( " warn " ) } ,
{ QStringLiteral ( " SBU停止 " ) , QStringLiteral ( " SBU STOP " ) , QStringLiteral ( " STOP SBU " ) , QStringLiteral ( " 自动引导因不可恢复错误进入安全备份状态。 " ) , QStringLiteral ( " 立即停止并使用人工引导,不得自行恢复。 " ) , QStringLiteral ( " stop " ) } ,
{ QStringLiteral ( " 速度过快 " ) , QStringLiteral ( " TOO FAST " ) , QStringLiteral ( " STOP TOO FAST " ) , QStringLiteral ( " 飞机速度超出系统可以安全处理的范围。 " ) , QStringLiteral ( " 立即停止飞机,等待系统重启或人工引导。 " ) , QStringLiteral ( " stop " ) } ,
{ QStringLiteral ( " 紧急停止 " ) , QStringLiteral ( " EMERGENCY STOP " ) , QStringLiteral ( " STOP " ) , QStringLiteral ( " 现场急停或急停线路断开已经触发。 " ) , QStringLiteral ( " 立即停止,等待现场确认和处置。 " ) , QStringLiteral ( " stop " ) } ,
{ QStringLiteral ( " 挡轮挡 " ) , QStringLiteral ( " CHOCKS ON " ) , QStringLiteral ( " CHOCK ON " ) , QStringLiteral ( " 轮挡区域人员检测已完成。 " ) , QStringLiteral ( " 保持停稳,进入地面保障流程。 " ) , QStringLiteral ( " good " ) } ,
{ QStringLiteral ( " 系统错误 " ) , QStringLiteral ( " ERROR " ) , QStringLiteral ( " ERROR " ) , QStringLiteral ( " 系统发生错误,自动引导不可继续。 " ) , QStringLiteral ( " 停止自动引导并通知维护人员。 " ) , QStringLiteral ( " stop " ) } ,
{ QStringLiteral ( " 系统故障 " ) , QStringLiteral ( " SYSTEM BREAKDOWN " ) , QStringLiteral ( " BLACK " ) , QStringLiteral ( " 系统发生严重故障,自动引导不可用。 " ) , QStringLiteral ( " 使用人工备份程序。 " ) , QStringLiteral ( " maint " ) } ,
{ QStringLiteral ( " 电源故障 " ) , QStringLiteral ( " POWER FAILURE " ) , QStringLiteral ( " BLACK " ) , QStringLiteral ( " 显示和引导系统电源中断。 " ) , QStringLiteral ( " 使用人工备份程序并检查供电。 " ) , QStringLiteral ( " maint " ) }
} } ;
if ( stateCode < 1 | | stateCode > static_cast < int > ( states . size ( ) ) ) {
stateCode = 12 ;
}
return states [ static_cast < size_t > ( stateCode - 1 ) ] ;
}
2026-09-17 15:30:30 +08:00
void MainWindow : : AddStatusRow ( const QString & caption , QLabel * & valueLabel )
{
const int row = ui - > resultGrid - > rowCount ( ) ;
auto * captionLabel = new QLabel ( caption , ui - > sidePanel ) ;
captionLabel - > setStyleSheet (
QStringLiteral ( " color: rgb(153, 164, 178); font-size: 24px; " ) ) ;
valueLabel = new QLabel ( ui - > sidePanel ) ;
valueLabel - > setAlignment ( Qt : : AlignRight | Qt : : AlignVCenter ) ;
valueLabel - > setTextInteractionFlags ( Qt : : TextSelectableByMouse ) ;
valueLabel - > setStyleSheet (
QStringLiteral ( " color: rgb(239, 242, 246); font-size: 26px; font-weight: 500; " ) ) ;
valueLabel - > setText ( QStringLiteral ( " -- " ) ) ;
ui - > resultGrid - > addWidget ( captionLabel , row , 0 ) ;
ui - > resultGrid - > addWidget ( valueLabel , row , 1 ) ;
}
void MainWindow : : ApplyPanelConfig ( const QJsonObject & panel )
{
m_requireLogin = panel . value ( " requireLogin " ) . toBool ( true ) ;
m_passwordValidSeconds = panel . value ( " passwordValidSeconds " ) . toInt ( 120 ) ;
if ( m_passwordValidSeconds < 1 ) {
m_passwordValidSeconds = 120 ;
}
if ( panel . contains ( QStringLiteral ( " workMode " ) ) ) {
m_maintenanceMode = panel . value ( " workMode " ) . toString ( ) = =
QStringLiteral ( " maintenance " ) ;
} else {
m_maintenanceMode = panel . value ( " maintenanceMode " ) . toBool ( ) ;
}
if ( ! m_requireLogin ) {
m_loggedIn = true ;
if ( m_client ) {
m_client - > RequestSessions ( 50 ) ;
}
}
RebuildModelCombo ( panel . value ( " models " ) . toArray ( ) ) ;
if ( panel . contains ( QStringLiteral ( " activated " ) ) ) {
ApplyActivatedLabel ( m_activatedLabel ,
panel . value ( " activated " ) . toBool ( ) ) ;
}
if ( m_workModeLabel ) {
m_workModeLabel - > setText ( m_maintenanceMode
? QStringLiteral ( " 维护模式 " )
: QStringLiteral ( " 正常工作模式 " ) ) ;
}
UpdateControls ( ) ;
}
void MainWindow : : RebuildModelCombo ( const QJsonArray & models )
{
const QString current = ui - > modelCombo - > currentText ( ) ;
const QSignalBlocker blocker ( ui - > modelCombo ) ;
ui - > modelCombo - > clear ( ) ;
ui - > modelCombo - > addItem ( QStringLiteral ( " 请选择机型 " ) ) ;
for ( const QJsonValue & value : models ) {
const QString name = value . isObject ( )
? value . toObject ( ) . value ( " name " ) . toString ( ) . trimmed ( )
: value . toString ( ) . trimmed ( ) ;
if ( ! name . isEmpty ( ) ) {
ui - > modelCombo - > addItem ( name ) ;
}
}
if ( ui - > modelCombo - > count ( ) < = 1 ) {
ui - > modelCombo - > addItem ( QStringLiteral ( " A320 " ) ) ;
ui - > modelCombo - > addItem ( QStringLiteral ( " B737 " ) ) ;
ui - > modelCombo - > addItem ( QStringLiteral ( " 其他 " ) ) ;
}
const int index = ui - > modelCombo - > findText ( current ) ;
ui - > modelCombo - > setCurrentIndex ( index > 0 ? index : 0 ) ;
}
void MainWindow : : ApplySessionList ( const QJsonArray & sessions )
{
if ( ! m_sessionCombo ) {
return ;
}
const QSignalBlocker blocker ( m_sessionCombo ) ;
m_sessionCombo - > clear ( ) ;
m_sessionCombo - > addItem ( QStringLiteral ( " 选择历史会话 " ) ) ;
for ( const QJsonValue & value : sessions ) {
const QJsonObject object = value . toObject ( ) ;
const QString uuid = object . value ( " sessionUuid " ) . toString ( ) ;
if ( uuid . isEmpty ( ) ) {
continue ;
}
const QString model = NonEmpty (
object . value ( " expectedModelType " ) . toString ( ) ,
NonEmpty ( object . value ( " modelType " ) . toString ( ) ,
NonEmpty ( object . value ( " expectedModel " ) . toString ( ) ,
object . value ( " recognizedModel " ) . toString ( ) ) ) ) ;
const QString label = QStringLiteral ( " %1 %2 " )
. arg ( FormatProtocolTime ( object . value ( " startTime " ) ) ,
NonEmpty ( model , QStringLiteral ( " 未选机型 " ) ) ) ;
m_sessionCombo - > addItem ( label , uuid ) ;
}
}
void MainWindow : : StartReplay ( const QJsonArray & samples )
{
StopReplay ( ) ;
m_replaySamples = samples ;
m_replayIndex = 0 ;
if ( m_replaySamples . isEmpty ( ) ) {
return ;
}
m_replaying = true ;
PlayNextReplaySample ( ) ;
}
void MainWindow : : StopReplay ( )
{
m_replaying = false ;
m_replaySamples = QJsonArray ( ) ;
m_replayIndex = 0 ;
if ( m_replayTimer ) {
m_replayTimer - > stop ( ) ;
}
}
void MainWindow : : PlayNextReplaySample ( )
{
if ( ! m_replaying | | m_replayIndex > = m_replaySamples . size ( ) ) {
StopReplay ( ) ;
UpdateControls ( ) ;
return ;
}
const QJsonObject sample = m_replaySamples . at ( m_replayIndex ) . toObject ( ) ;
m_replaying = false ;
ApplyStatus ( sample ) ;
m_replaying = true ;
+ + m_replayIndex ;
if ( m_replayIndex > = m_replaySamples . size ( ) ) {
StopReplay ( ) ;
UpdateControls ( ) ;
return ;
}
const qint64 currentTs =
sample . value ( " timestamp " ) . toVariant ( ) . toLongLong ( ) ;
const qint64 nextTs = m_replaySamples . at ( m_replayIndex )
. toObject ( )
. value ( " timestamp " )
. toVariant ( )
. toLongLong ( ) ;
int intervalMs = static_cast < int > ( nextTs - currentTs ) ;
if ( intervalMs < 50 ) {
intervalMs = 50 ;
} else if ( intervalMs > 1000 ) {
intervalMs = 1000 ;
}
m_replayTimer - > start ( intervalMs ) ;
}
QString MainWindow : : PromptControlPassword ( const QString & title )
{
if ( ! m_requireLogin ) {
return QStringLiteral ( " " ) ;
}
if ( IsPasswordSessionValid ( ) & & m_client ) {
const QString cached = m_client - > SessionPassword ( ) ;
if ( ! cached . isEmpty ( ) ) {
return cached ;
}
}
DialogControlPassword dialog ( this ) ;
dialog . SetTitle ( title ) ;
if ( m_client ) {
const QString expected = m_client - > SessionPassword ( ) ;
if ( ! expected . isEmpty ( ) ) {
dialog . SetPasswordChecker ( [ expected ] ( const QString & password ) {
return password = = expected ;
} ) ;
}
}
if ( dialog . exec ( ) ! = QDialog : : Accepted ) {
return QString ( ) ;
}
if ( m_client ) {
m_client - > SetSessionPassword ( dialog . Password ( ) ) ;
}
BeginPasswordSession ( ) ;
return dialog . Password ( ) ;
}
bool MainWindow : : IsPasswordSessionValid ( ) const
{
return m_loggedIn & &
m_passwordValidUntilMs > QDateTime : : currentMSecsSinceEpoch ( ) ;
}
void MainWindow : : BeginPasswordSession ( )
{
// 从本次成功输入起计算有效期,到期后必须重新输入密码。
m_loggedIn = true ;
const int validSeconds = m_passwordValidSeconds < 1
? 120
: m_passwordValidSeconds ;
m_passwordValidUntilMs = QDateTime : : currentMSecsSinceEpoch ( ) +
static_cast < qint64 > ( validSeconds ) * 1000 ;
if ( m_passwordExpireTimer ) {
m_passwordExpireTimer - > start ( validSeconds * 1000 ) ;
}
UpdateControls ( ) ;
}
void MainWindow : : ExpirePasswordSession ( )
{
m_loggedIn = false ;
m_passwordValidUntilMs = 0 ;
if ( m_passwordExpireTimer ) {
m_passwordExpireTimer - > stop ( ) ;
}
if ( m_client ) {
m_client - > ClearSessionPassword ( ) ;
}
if ( m_passwordEdit ) {
m_passwordEdit - > clear ( ) ;
}
UpdateControls ( ) ;
}
void MainWindow : : RecordChockWithPassword ( bool chockOn )
{
if ( ! m_client | | ! m_client - > IsConnected ( ) ) {
return ;
}
const QString title = chockOn
? QStringLiteral ( " 上轮挡 " )
: QStringLiteral ( " 下轮挡 " ) ;
const QString password = PromptControlPassword ( title ) ;
if ( m_requireLogin & & password . isEmpty ( ) ) {
return ;
}
m_commandPending = true ;
UpdateControls ( ) ;
m_client - > RecordChock ( chockOn , password ) ;
}