CI / test (push) Canceled after 0s
fix các bug theo yêu cầu https://fptsoftware362-my.sharepoint.com/❌/g/personal/nampdt_fpt_com/IQAHBJ4A9xqDTLgvt2bhukJEAdRB5LRz2hbJpTivvIiBSYM?wdExp=TEAMS-TREATMENT&web=1&isSPOFile=1&ovuser=f01e930a-b52e-42b1-b70f-a8882b5d043b%2CAnhTNM1%40fpt.com&clickparams=eyJBcHBOYW1lIjoiVGVhbXMtRGVza3RvcCIsIkFwcFZlcnNpb24iOiI0OS8yNjA4MTMxOTMxNyIsIkhhc0ZlZGVyYXRlZFVzZXIiOmZhbHNlfQ%3D%3D --------- Co-authored-by: Duy Le Huu <duylh19@fpt.com> Reviewed-on: #10 Co-authored-by: Anh Tran Nguyen Minh <anhtnm1@fpt.com>
109 lines
4.4 KiB
Python
109 lines
4.4 KiB
Python
"""Lớp phủ "đang xử lý" ở cấp cửa sổ, dành cho tác vụ chặn GUI thread.
|
|
|
|
Vì sao là file riêng chứ không nhét vào ``main_window.py``: file đó chỉ còn 9
|
|
dòng vật lý dưới trần 400 của Gate S, và một lớp phủ cấp cửa sổ là một trách
|
|
nhiệm riêng (guardrail G6).
|
|
|
|
Cùng lý do ``repaint()`` với panel bận của GraphRAG — xem
|
|
``presentation/graph/structure_graph_view.py:139-151``.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from time import perf_counter
|
|
from typing import Callable
|
|
|
|
from PySide6.QtCore import Qt
|
|
from PySide6.QtWidgets import QHBoxLayout, QLabel, QVBoxLayout, QWidget
|
|
|
|
# Dưới mức này người dùng chưa kịp nhận ra mình đang đợi, nên một lớp phủ toàn
|
|
# cửa sổ chỉ kịp nháy lên rồi tắt — tự nó là một khuyết tật giao diện, không
|
|
# phải một lời trấn an.
|
|
_NOTICEABLE_MS = 400.0
|
|
|
|
|
|
class BusyOverlay(QWidget):
|
|
"""A window-wide "please wait" cover for work that blocks the GUI thread.
|
|
|
|
Deliberately NOT registered with :func:`i18n.on_language_changed`: the text
|
|
is supplied by the caller right before the block and must stay in the
|
|
language the rest of the screen is still showing.
|
|
"""
|
|
|
|
def __init__(self, parent: QWidget) -> None:
|
|
"""Build the cover hidden; it sizes itself to the parent on every show."""
|
|
super().__init__(parent)
|
|
self.setObjectName("busyOverlay")
|
|
# A QWidget SUBCLASS ignores a stylesheet background without this
|
|
# attribute; a plain QWidget instance (the panel below) does not need it.
|
|
self.setAttribute(Qt.WA_StyledBackground, True)
|
|
self.setFocusPolicy(Qt.NoFocus)
|
|
# Chưa đo được lượt nào: xem mục ``run_blocking``.
|
|
self._last_ms: float | None = None
|
|
lay = QVBoxLayout(self)
|
|
lay.setContentsMargins(0, 0, 0, 0)
|
|
lay.addStretch(1)
|
|
row = QHBoxLayout()
|
|
row.addStretch(1)
|
|
self._panel = QWidget()
|
|
self._panel.setObjectName("busyOverlayPanel")
|
|
inner = QHBoxLayout(self._panel)
|
|
inner.setContentsMargins(24, 18, 24, 18)
|
|
self._label = QLabel()
|
|
inner.addWidget(self._label)
|
|
row.addWidget(self._panel)
|
|
row.addStretch(1)
|
|
lay.addLayout(row)
|
|
lay.addStretch(1)
|
|
self.hide()
|
|
|
|
def text(self) -> str:
|
|
"""Chữ đang hiện trên lớp phủ (dùng cho test)."""
|
|
return self._label.text()
|
|
|
|
def run_blocking(self, message: str, work: Callable[[], None]) -> None:
|
|
"""Run ``work`` on the GUI thread, covered only when that is worth doing.
|
|
|
|
Nothing can time the freeze WHILE it happens: the GUI thread stops, so
|
|
no timer fires and no watchdog can raise the cover mid-way. The only
|
|
honest clock is the PREVIOUS run of this same call, so that is what
|
|
decides. No measurement yet (the first switch of a process) errs
|
|
towards showing: one flash is a smaller defect than a multi-second
|
|
freeze with nothing on screen to explain it.
|
|
|
|
The result is self-calibrating. A fast machine flashes once per launch
|
|
and then stays out of the way; a slow one, or a big skill library, gets
|
|
the cover on every switch from the second one on.
|
|
|
|
``work`` is timed and its exceptions propagate — the cover still comes
|
|
down, so a raising callback cannot leave it stuck on screen forever.
|
|
"""
|
|
if self._last_ms is None or self._last_ms >= _NOTICEABLE_MS:
|
|
self.show_busy(message)
|
|
started = perf_counter()
|
|
try:
|
|
work()
|
|
finally:
|
|
self._last_ms = (perf_counter() - started) * 1000.0
|
|
self.hide_busy()
|
|
|
|
def show_busy(self, message: str) -> None:
|
|
"""Show the cover and FORCE it onto the screen right now.
|
|
|
|
``repaint()``, not ``update()``: the caller is about to block the GUI
|
|
thread, so a queued paint would only run once the freeze is over — the
|
|
one moment the cover is no longer needed.
|
|
|
|
No animated progress bar on purpose: with no event loop running,
|
|
nothing would move; only static text is guaranteed to be readable.
|
|
"""
|
|
self._label.setText(message)
|
|
self.setGeometry(self.parent().rect())
|
|
self.show()
|
|
self.raise_()
|
|
self.repaint()
|
|
|
|
def hide_busy(self) -> None:
|
|
"""Release the cover. Call from ``finally`` so a raising callback
|
|
cannot leave it stuck on screen forever."""
|
|
self.hide()
|