fix(tray): fenêtres invisibles et coche de profil non mise à jour
- Ajoute _present_window() (deiconify/lift/topmost/focus_force) pour que les Toplevel enfants d'une racine withdraw() apparaissent bien au premier plan sous Windows (logs, profil, ajout, authentification). - Rend le sous-menu des profils dynamique via un générateur avec items radio + checked=, et appelle icon.update_menu() après changement de profil pour que la coche suive l'état courant.
This commit is contained in:
+60
-14
@@ -68,31 +68,53 @@ class TrayApp:
|
||||
root = self.get_root()
|
||||
root.after(0, fn)
|
||||
|
||||
def _present_window(self, win) -> None:
|
||||
"""Force une fenêtre Tk à apparaître au premier plan.
|
||||
|
||||
La racine étant retirée (``withdraw``), les ``Toplevel`` enfants
|
||||
peuvent apparaître cachés ou derrière les autres fenêtres sous
|
||||
Windows. On les rend visibles, on leur donne le focus, et on les
|
||||
place brièvement en topmost pour garantir qu'ils soient au-dessus.
|
||||
"""
|
||||
try:
|
||||
win.deiconify()
|
||||
win.lift()
|
||||
win.attributes("-topmost", True)
|
||||
win.after(200, lambda: win.attributes("-topmost", False))
|
||||
win.focus_force()
|
||||
except tk.TclError:
|
||||
pass
|
||||
|
||||
# -- actions du menu -------------------------------------------------------
|
||||
|
||||
def _show_logs(self) -> None:
|
||||
LogsWindow(self.get_root())
|
||||
win = LogsWindow(self.get_root())
|
||||
self._present_window(win)
|
||||
|
||||
def _edit_profile(self, name: str | None = None) -> None:
|
||||
"""Ouvre l'éditeur du profil `name`, ou le profil actif si `None`."""
|
||||
store = self.store
|
||||
target = store.get(name) if name else store.active()
|
||||
dlg = ProfileDialog(self.get_root(), existing=target, secure=self.secure)
|
||||
self._present_window(dlg)
|
||||
self.get_root().wait_window(dlg)
|
||||
if dlg.result:
|
||||
try:
|
||||
store.upsert(dlg.result)
|
||||
store.set_active(dlg.result.name)
|
||||
self._refresh_menu()
|
||||
except Exception as exc:
|
||||
LOG.exception("Impossible d'enregistrer le profil : %s", exc)
|
||||
|
||||
def _add_profile(self) -> None:
|
||||
dlg = ProfileDialog(self.get_root(), existing=None, secure=self.secure)
|
||||
self._present_window(dlg)
|
||||
self.get_root().wait_window(dlg)
|
||||
if dlg.result:
|
||||
try:
|
||||
self.store.upsert(dlg.result)
|
||||
LOG.info("Profil « %s » ajouté.", dlg.result.name)
|
||||
self._refresh_menu()
|
||||
except Exception as exc:
|
||||
LOG.exception("Impossible d'ajouter le profil : %s", exc)
|
||||
|
||||
@@ -100,11 +122,21 @@ class TrayApp:
|
||||
try:
|
||||
self.store.set_active(name)
|
||||
LOG.info("Profil actif : %s", name)
|
||||
self._refresh_menu()
|
||||
except Exception as exc:
|
||||
LOG.exception("Impossible de sélectionner le profil : %s", exc)
|
||||
|
||||
def _refresh_menu(self) -> None:
|
||||
"""Demande à pystray de reconstruire le menu (met à jour les coches)."""
|
||||
if self._icon is not None:
|
||||
try:
|
||||
self._icon.update_menu()
|
||||
except Exception:
|
||||
LOG.debug("update_menu() a échoué", exc_info=True)
|
||||
|
||||
def _manage_auth(self) -> None:
|
||||
dlg = AuthDialog(self.get_root(), self.secure)
|
||||
self._present_window(dlg)
|
||||
self.get_root().wait_window(dlg)
|
||||
|
||||
# -- construction de l'icône ----------------------------------------------
|
||||
@@ -126,19 +158,10 @@ class TrayApp:
|
||||
menu_items.append(
|
||||
self._menu_item("Ajouter un profil", self._guard("Ajouter un profil", self._add_profile))
|
||||
)
|
||||
# Sous-menu des profils
|
||||
profiles_sub = pystray.Menu(
|
||||
*[
|
||||
self._menu_item(
|
||||
p.name + (" ✓" if p.name == self.store.active_name else ""),
|
||||
self._guard(
|
||||
f"sélection du profil « {p.name} »",
|
||||
self._select_profile_action(p),
|
||||
),
|
||||
)
|
||||
for p in self.store.get_all()
|
||||
]
|
||||
)
|
||||
# Sous-menu des profils — construit dynamiquement à chaque affichage
|
||||
# (grâce au callable), de sorte que la coche « ✓ » et la liste des
|
||||
# profils reflètent toujours l'état courant après update_menu().
|
||||
profiles_sub = pystray.Menu(self._profile_menu_items)
|
||||
menu_items.append(self._menu_item("Modifier le profil", None, submenu=profiles_sub))
|
||||
|
||||
menu_items.append(pystray.Menu.SEPARATOR)
|
||||
@@ -160,6 +183,29 @@ class TrayApp:
|
||||
pystray.Menu(*menu_items),
|
||||
)
|
||||
|
||||
def _profile_menu_items(self):
|
||||
"""Génère dynamiquement les items du sous-menu des profils.
|
||||
|
||||
Appelé par pystray à chaque (re)construction du menu : la coche
|
||||
(via ``checked=``, un item radio) suit donc toujours le profil actif.
|
||||
"""
|
||||
import pystray
|
||||
|
||||
for p in self.store.get_all():
|
||||
yield pystray.MenuItem(
|
||||
p.name,
|
||||
self._guard(
|
||||
f"sélection du profil « {p.name} »",
|
||||
self._select_profile_action(p),
|
||||
),
|
||||
checked=self._make_checked(p.name),
|
||||
radio=True,
|
||||
)
|
||||
|
||||
def _make_checked(self, profile_name: str) -> Callable:
|
||||
"""Retourne un prédicat évalué à l'affichage du menu."""
|
||||
return lambda item: self.store.active_name == profile_name
|
||||
|
||||
def _menu_item(self, text: str, action, submenu=None):
|
||||
import pystray
|
||||
|
||||
|
||||
Reference in New Issue
Block a user