Distributed confirmation dialog: all GUIs open on WAIT_USER, server closes all
- Browser: OK button sends Confirm without closing dialog; Escape blocked; dialog closes when state leaves WAIT_USER (server-driven). - PyQt: non-blocking QDialog replaces blocking QMessageBox; OK sends Confirm without closing; dialog closed by server state change or on disconnect. - ws_client.py: remove deprecated loop= params from websockets.connect() and asyncio.ensure_future() (same fix already applied to ws_server.py) so the PyQt client can actually connect with websockets >= 10. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tqxrk8uj4M3w3d3eXm3xK8
This commit is contained in:
+21
-5
@@ -421,6 +421,7 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self.sud_empty = True
|
||||
self.sud_state = None
|
||||
self.sud_user_message = None
|
||||
self._confirm_box = None
|
||||
# Mirrors the server's TempController.enabled - gates the
|
||||
# temp/heatrate setpoint controls (see on_tempctrl_changed()).
|
||||
self.tc_enabled = False
|
||||
@@ -732,6 +733,7 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self.sud_empty = True
|
||||
self.sud_state = None
|
||||
self.sud_user_message = None
|
||||
self._close_confirm_dialog()
|
||||
self.tc_enabled = False
|
||||
self.checkbox_tc_enable.blockSignals(True)
|
||||
self.checkbox_tc_enable.setChecked(False)
|
||||
@@ -875,11 +877,23 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self.msg_sud.send({'Stop': True})
|
||||
|
||||
def show_user_message(self, message):
|
||||
# on_sud_changed() already runs on the GUI thread (via recv_signal),
|
||||
# so this can show the dialog directly. The Sud is already blocked
|
||||
# in WAIT_USER server-side; closing this dialog is what unblocks it.
|
||||
QtWidgets.QMessageBox.information(self, "Sud", message)
|
||||
self.msg_sud.send({'Confirm': True})
|
||||
if self._confirm_box is not None:
|
||||
return
|
||||
dlg = QtWidgets.QDialog(self)
|
||||
dlg.setWindowTitle("Sud")
|
||||
dlg.setWindowModality(QtCore.Qt.NonModal)
|
||||
layout = QtWidgets.QVBoxLayout(dlg)
|
||||
layout.addWidget(QtWidgets.QLabel(message))
|
||||
btn = QtWidgets.QPushButton("OK")
|
||||
btn.clicked.connect(lambda: self.msg_sud.send({'Confirm': True}))
|
||||
layout.addWidget(btn)
|
||||
self._confirm_box = dlg
|
||||
dlg.show()
|
||||
|
||||
def _close_confirm_dialog(self):
|
||||
if self._confirm_box is not None:
|
||||
self._confirm_box.close()
|
||||
self._confirm_box = None
|
||||
|
||||
def on_action_sud_new(self):
|
||||
# Sends an empty schedule via the same Load path Load Sud uses, with
|
||||
@@ -1088,6 +1102,8 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
|
||||
if self.sud_state == SUD_WAIT_USER_STATE and prev_state != SUD_WAIT_USER_STATE and self.sud_user_message:
|
||||
self.show_user_message(self.sud_user_message)
|
||||
elif prev_state == SUD_WAIT_USER_STATE and self.sud_state != SUD_WAIT_USER_STATE:
|
||||
self._close_confirm_dialog()
|
||||
self._update_step_plates()
|
||||
elif "UserMessage" in key:
|
||||
self.sud_user_message = msg['UserMessage']
|
||||
|
||||
+5
-1
@@ -483,6 +483,9 @@ function onSudChanged(msg) {
|
||||
sudState = newState;
|
||||
if (newState === WAIT_USER_STATE && prevState !== WAIT_USER_STATE && sudUserMessage) {
|
||||
showUserMessage(sudUserMessage);
|
||||
} else if (prevState === WAIT_USER_STATE && newState !== WAIT_USER_STATE) {
|
||||
const dlg = document.getElementById('sud-message-dialog');
|
||||
if (dlg.open) dlg.close();
|
||||
}
|
||||
updateStepPlates();
|
||||
updateSudActions();
|
||||
@@ -678,7 +681,8 @@ document.getElementById('btn-sud-pause').addEventListener('click', () => {
|
||||
document.getElementById('btn-sud-stop').addEventListener('click', () => {
|
||||
sendMsg('Sud', {Stop: true});
|
||||
});
|
||||
document.getElementById('sud-message-dialog').addEventListener('close', () => {
|
||||
document.getElementById('sud-message-dialog').addEventListener('cancel', e => e.preventDefault());
|
||||
document.getElementById('sud-message-ok').addEventListener('click', () => {
|
||||
sendMsg('Sud', {Confirm: true});
|
||||
});
|
||||
|
||||
|
||||
+2
-2
@@ -100,8 +100,8 @@
|
||||
|
||||
<dialog id="sud-message-dialog">
|
||||
<p id="sud-message-text"></p>
|
||||
<form method="dialog">
|
||||
<button value="ok">OK</button>
|
||||
<form>
|
||||
<button id="sud-message-ok" type="button">OK</button>
|
||||
</form>
|
||||
</dialog>
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ class WsClient:
|
||||
def bg_run(self, uri):
|
||||
print("bg_run: started")
|
||||
asyncio.set_event_loop(self.loop)
|
||||
fut = asyncio.ensure_future(self.run_client(uri), loop=self.loop)
|
||||
fut = asyncio.ensure_future(self.run_client(uri))
|
||||
self.loop.run_until_complete(fut)
|
||||
self.stop = None
|
||||
print("bg_run: terminated")
|
||||
@@ -38,7 +38,7 @@ class WsClient:
|
||||
|
||||
async def run_client(self, uri):
|
||||
try:
|
||||
websocket = await websockets.connect(uri, loop=self.loop)
|
||||
websocket = await websockets.connect(uri)
|
||||
except Exception as exc:
|
||||
print(f"Failed to connect to {uri}: {exc}.")
|
||||
return
|
||||
@@ -47,8 +47,8 @@ class WsClient:
|
||||
await self.listener.on_connect()
|
||||
try:
|
||||
path = "/"
|
||||
consumer_task = asyncio.ensure_future(self.handler_recv(websocket, path), loop=self.loop)
|
||||
producer_task = asyncio.ensure_future(self.handler_send(websocket, path), loop=self.loop)
|
||||
consumer_task = asyncio.ensure_future(self.handler_recv(websocket, path))
|
||||
producer_task = asyncio.ensure_future(self.handler_send(websocket, path))
|
||||
done, pending = await asyncio.wait([consumer_task, producer_task, self.stop], return_when=asyncio.FIRST_COMPLETED,)
|
||||
for task in pending:
|
||||
task.cancel()
|
||||
|
||||
Reference in New Issue
Block a user