1212from typing import TYPE_CHECKING
1313
1414from PyQt5 .QtCore import Qt , pyqtSignal , QPoint , QTimer
15- from PyQt5 .QtGui import QMouseEvent , QIcon , QPixmap
15+ from PyQt5 .QtGui import QColor , QMouseEvent , QIcon , QPixmap
1616from PyQt5 .QtWidgets import (
1717 QApplication ,
1818 QCheckBox ,
3939)
4040
4141from .plugin_state import PluginStateManager , PluginState
42- from .plugin_base import WindowMode , LogLevel
42+ from .plugin_base import PluginLifecycle , WindowMode , LogLevel
4343from .app_paths import get_data_dir
4444
4545if TYPE_CHECKING :
@@ -214,6 +214,15 @@ def _detach_tab(self, index: int, name: str, pos: QPoint | None = None) -> None:
214214
215215 self .tab_detached .emit (name )
216216
217+ def _cleanup_detached (self , name : str ) -> None :
218+ """安全清理 detached 窗口引用"""
219+ if name in self ._detached_windows :
220+ w = self ._detached_windows [name ]
221+ w .blockSignals (True )
222+ w .close ()
223+ w .deleteLater ()
224+ del self ._detached_windows [name ]
225+
217226 def _attach_tab (self , name : str ) -> None :
218227 """将弹出的窗口嵌回标签页"""
219228 if name not in self ._detached_windows :
@@ -444,6 +453,10 @@ def __init__(self, plugin_manager: PluginManager, parent=None):
444453 # 应用已保存的状态到插件
445454 self ._apply_saved_states ()
446455
456+ # 连接所有插件的 ready 信号,就绪后自动刷新列表
457+ for p in self ._manager .plugins .values ():
458+ p .ready .connect (lambda _p = p : self ._on_plugin_ready (_p ))
459+
447460 self ._refresh_plugin_list ()
448461
449462 # 定时刷新连接状态
@@ -672,6 +685,13 @@ def _stop_debug(self) -> None:
672685
673686 # ── 插件列表 ────────────────────────────────────────
674687
688+ def _on_plugin_ready (self , plugin ) -> None :
689+ """插件初始化完成,刷新列表显示"""
690+ self ._refresh_plugin_list ()
691+ self .statusBar ().showMessage (
692+ self .tr ("插件 {name} 就绪" ).format (name = plugin .name ), 2000
693+ )
694+
675695 def _refresh_plugin_list (self ) -> None :
676696 """刷新插件列表和标签页"""
677697 t = self ._tab_widget
@@ -696,12 +716,23 @@ def _refresh_plugin_list(self) -> None:
696716 li = QListWidgetItem (p .name )
697717 li .setData (Qt .UserRole , name )
698718 li .setIcon (p .plugin_icon )
699- # 已禁用的用灰色
700- if not p .is_enabled :
701- li .setForeground (Qt .gray )
719+
720+ lc = p .lifecycle
721+
722+ # 根据生命周期状态显示
723+ if lc == PluginLifecycle .INITIALIZING :
724+ li .setForeground (QColor ("#f57c00" )) # 橙色:初始化中
725+ li .setText (f"{ p .name } (⏳ 初始化中...)" )
726+ elif not p .is_enabled :
727+ li .setForeground (Qt .gray ) # 灰色:已禁用
728+ elif lc == PluginLifecycle .SHUTTING_DOWN :
729+ li .setForeground (QColor ("#c62828" )) # 红色:关闭中
730+ li .setText (f"{ p .name } (⏸ 关闭中...)" )
731+
702732 lst .addItem (li )
703733
704- if p .widget and name not in t ._detached_windows and name not in self ._closed_plugins :
734+ # 只有就绪状态才创建窗口(INITIALIZING/STOPPED 不创建)
735+ if p .lifecycle == PluginLifecycle .READY and p .widget and name not in t ._detached_windows and name not in self ._closed_plugins :
705736 st = self ._effective_state (name )
706737 if st .window_mode == WindowMode .DETACHED :
707738 t .add_detachable_tab (p .widget , name , icon = p .plugin_icon )
@@ -748,11 +779,13 @@ def _on_list_context_menu(self, pos) -> None:
748779 menu = QMenu (self )
749780 t = self ._tab_widget
750781
751- # 启用/禁用
782+ # 启用/禁用(非就绪状态不可切换,但 STOPPED 可以重新启用)
783+ lc = plugin .lifecycle
784+ can_control = lc in (PluginLifecycle .READY , PluginLifecycle .STOPPED )
752785 act_enable = menu .addAction ("✅ " + self .tr ("启用" ))
753786 act_disable = menu .addAction ("❌ " + self .tr ("禁用" ))
754- act_enable .setEnabled (not plugin .is_enabled )
755- act_disable .setEnabled (plugin .is_enabled )
787+ act_enable .setEnabled (can_control and not plugin .is_enabled )
788+ act_disable .setEnabled (lc == PluginLifecycle . READY and plugin .is_enabled )
756789 act_enable .triggered .connect (lambda : self ._toggle_plugin (name , True ))
757790 act_disable .triggered .connect (lambda : self ._toggle_plugin (name , False ))
758791
@@ -777,9 +810,10 @@ def _on_list_context_menu(self, pos) -> None:
777810 act_open = menu .addAction ("🖥 " + self .tr ("打开窗口" ))
778811 act_close = menu .addAction ("🚫 " + self .tr ("关闭窗口" ))
779812
780- can_open = (has_closed or (not has_tab and plugin .widget is not None ))
813+ # 窗口操作只有就绪状态才可用
814+ can_open = lc == PluginLifecycle .READY and (has_closed or (not has_tab and plugin .widget is not None ))
781815 act_open .setEnabled (can_open )
782- act_close .setEnabled (has_tab or has_detached )
816+ act_close .setEnabled (lc == PluginLifecycle . READY and ( has_tab or has_detached ) )
783817
784818 # 打开日志文件
785819 act_log = menu .addAction ("📋 " + self .tr ("打开日志" ))
@@ -801,6 +835,8 @@ def _toggle_plugin(self, name: str, enable: bool) -> None:
801835 if enable :
802836 self ._manager .enable_plugin (name )
803837 else :
838+ # 禁用时先关闭窗口(处理 detached 窗口清理),再 shutdown
839+ self ._close_plugin_window (name )
804840 self ._manager .disable_plugin (name )
805841 self ._sync_state (name , enabled = enable )
806842 self ._refresh_plugin_list ()
@@ -831,15 +867,6 @@ def _open_plugin_window(self, name: str) -> None:
831867 self ._closed_plugins .discard (name )
832868 t .add_detachable_tab (plugin .widget , name , icon = plugin .plugin_icon )
833869
834- def _cleanup_detached (self , name : str ) -> None :
835- """安全清理 detached 窗口引用"""
836- if name in self ._detached_windows :
837- w = self ._detached_windows [name ]
838- w .blockSignals (True ) # 阻止 closeEvent 再次触发 embed_requested
839- w .close ()
840- w .deleteLater ()
841- del self ._detached_windows [name ]
842-
843870 def _close_plugin_window (self , name : str ) -> None :
844871 """关闭插件窗口(不销毁)"""
845872 t = self ._tab_widget
@@ -904,6 +931,7 @@ def _open_plugin_settings(self, name: str) -> None:
904931 if new_state .enabled :
905932 self ._manager .enable_plugin (name )
906933 else :
934+ self ._close_plugin_window (name )
907935 self ._manager .disable_plugin (name )
908936 # 立即应用日志级别
909937 plugin = self ._manager .plugins .get (name )
0 commit comments