MoleditPy-linux 4.9.3__tar.gz → 4.10.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/PKG-INFO +1 -1
  2. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/pyproject.toml +1 -1
  3. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/MoleditPy_linux.egg-info/PKG-INFO +1 -1
  4. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/plugins/plugin_manager.py +72 -2
  5. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/plugins/plugin_manager_window.py +61 -5
  6. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/main_window_init.py +0 -1
  7. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/plugin_menu_manager.py +0 -1
  8. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/LICENSE +0 -0
  9. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/README.md +0 -0
  10. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/setup.cfg +0 -0
  11. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/MoleditPy_linux.egg-info/SOURCES.txt +0 -0
  12. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/MoleditPy_linux.egg-info/dependency_links.txt +0 -0
  13. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/MoleditPy_linux.egg-info/entry_points.txt +0 -0
  14. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/MoleditPy_linux.egg-info/requires.txt +0 -0
  15. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/MoleditPy_linux.egg-info/top_level.txt +0 -0
  16. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/__init__.py +0 -0
  17. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/__main__.py +0 -0
  18. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/assets/file_icon.ico +0 -0
  19. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/assets/icon.icns +0 -0
  20. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/assets/icon.ico +0 -0
  21. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/assets/icon.png +0 -0
  22. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/core/__init__.py +0 -0
  23. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/core/mol_geometry.py +0 -0
  24. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/core/molecular_data.py +0 -0
  25. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/main.py +0 -0
  26. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/plugins/__init__.py +0 -0
  27. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/plugins/plugin_interface.py +0 -0
  28. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/__init__.py +0 -0
  29. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/about_dialog.py +0 -0
  30. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/align_plane_dialog.py +0 -0
  31. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/alignment_dialog.py +0 -0
  32. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/analysis_window.py +0 -0
  33. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/angle_dialog.py +0 -0
  34. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/app_state.py +0 -0
  35. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/atom_item.py +0 -0
  36. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/atom_picking.py +0 -0
  37. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/base_picking_dialog.py +0 -0
  38. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/bond_item.py +0 -0
  39. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/bond_length_dialog.py +0 -0
  40. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/calculation_worker.py +0 -0
  41. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/chain_mixin.py +0 -0
  42. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/color_settings_dialog.py +0 -0
  43. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/compute_logic.py +0 -0
  44. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/constrained_optimization_dialog.py +0 -0
  45. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/custom_interactor_style.py +0 -0
  46. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/custom_qt_interactor.py +0 -0
  47. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/dialog_3d_picking_mixin.py +0 -0
  48. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/dialog_logic.py +0 -0
  49. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/dihedral_dialog.py +0 -0
  50. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/edit_3d_logic.py +0 -0
  51. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/edit_actions_logic.py +0 -0
  52. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/export_logic.py +0 -0
  53. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/geometry_base_dialog.py +0 -0
  54. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/io_logic.py +0 -0
  55. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/main_window.py +0 -0
  56. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/mirror_dialog.py +0 -0
  57. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/molecular_scene_handler.py +0 -0
  58. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/molecule_scene.py +0 -0
  59. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/move_group_dialog.py +0 -0
  60. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/move_selected_atoms_dialog.py +0 -0
  61. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/periodic_table_dialog.py +0 -0
  62. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/planarize_dialog.py +0 -0
  63. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/preview_molecule.py +0 -0
  64. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/settings_dialog.py +0 -0
  65. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/settings_tabs/__init__.py +0 -0
  66. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/settings_tabs/settings_2d_cleanup_tab.py +0 -0
  67. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/settings_tabs/settings_2d_tab.py +0 -0
  68. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/settings_tabs/settings_3d_tabs.py +0 -0
  69. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/settings_tabs/settings_other_tab.py +0 -0
  70. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/settings_tabs/settings_tab_base.py +0 -0
  71. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/string_importers.py +0 -0
  72. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/template_preview_item.py +0 -0
  73. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/template_preview_view.py +0 -0
  74. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/translation_dialog.py +0 -0
  75. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/ui_manager.py +0 -0
  76. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/user_template_dialog.py +0 -0
  77. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/view_3d_logic.py +0 -0
  78. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/ui/zoomable_view.py +0 -0
  79. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/utils/__init__.py +0 -0
  80. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/utils/constants.py +0 -0
  81. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/utils/default_settings.py +0 -0
  82. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/utils/hit_radius.py +0 -0
  83. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/utils/sip_isdeleted_safe.py +0 -0
  84. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/utils/suppress_log.py +0 -0
  85. {moleditpy_linux-4.9.3 → moleditpy_linux-4.10.1}/src/moleditpy_linux/utils/system_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: MoleditPy-linux
3
- Version: 4.9.3
3
+ Version: 4.10.1
4
4
  Summary: A cross-platform, simple, and intuitive molecular structure editor built in Python. It allows 2D molecular drawing and 3D structure visualization. It supports exporting structure files for input to DFT calculation software.
5
5
  Author-email: HiroYokoyama <titech.yoko.hiro@gmail.com>
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
  [project]
6
6
  name = "MoleditPy-linux"
7
7
 
8
- version = "4.9.3"
8
+ version = "4.10.1"
9
9
 
10
10
  license = {file = "LICENSE"}
11
11
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: MoleditPy-linux
3
- Version: 4.9.3
3
+ Version: 4.10.1
4
4
  Summary: A cross-platform, simple, and intuitive molecular structure editor built in Python. It allows 2D molecular drawing and 3D structure visualization. It supports exporting structure files for input to DFT calculation software.
5
5
  Author-email: HiroYokoyama <titech.yoko.hiro@gmail.com>
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -14,6 +14,7 @@ from __future__ import annotations
14
14
  import ast
15
15
  import hashlib
16
16
  import importlib.util
17
+ import json
17
18
  import logging
18
19
  import os
19
20
  import shutil
@@ -80,6 +81,10 @@ class PluginManager:
80
81
  self.plugin_dir: str = os.path.join(
81
82
  os.path.expanduser("~"), ".moleditpy", "plugins"
82
83
  )
84
+ self.disabled_plugins_path: str = os.path.join(
85
+ os.path.expanduser("~"), ".moleditpy", "disabled_plugins.json"
86
+ )
87
+ self.disabled_plugins: set[str] = self._read_disabled_plugins()
83
88
  self.plugins: List[Dict[str, Any]] = [] # List of dicts
84
89
  self.main_window: Any = main_window
85
90
 
@@ -118,6 +123,42 @@ class PluginManager:
118
123
  except OSError as e:
119
124
  logging.warning(f"Error creating plugin directory: {e}")
120
125
 
126
+ def _read_disabled_plugins(self) -> set[str]:
127
+ """Read disabled plugin paths without making a malformed file fatal."""
128
+ try:
129
+ with open(self.disabled_plugins_path, "r", encoding="utf-8") as file:
130
+ data = json.load(file)
131
+ if isinstance(data, list):
132
+ values = data
133
+ elif isinstance(data, dict) and isinstance(
134
+ data.get("disabled_plugins"), list
135
+ ):
136
+ values = data["disabled_plugins"]
137
+ else:
138
+ values = []
139
+ return {str(value).replace("\\", "/") for value in values if value}
140
+ except (OSError, TypeError, ValueError, json.JSONDecodeError):
141
+ return set()
142
+
143
+ def save_disabled_plugins(self, disabled_paths: set[str]) -> None:
144
+ """Persist disabled plugin paths for the next application launch."""
145
+ normalized = sorted(path.replace("\\", "/") for path in disabled_paths if path)
146
+ try:
147
+ os.makedirs(os.path.dirname(self.disabled_plugins_path), exist_ok=True)
148
+ with open(self.disabled_plugins_path, "w", encoding="utf-8") as file:
149
+ json.dump(normalized, file, indent=2, ensure_ascii=False)
150
+ self.disabled_plugins = set(normalized)
151
+ except (OSError, TypeError, ValueError):
152
+ logging.warning("Unable to save disabled plugin list", exc_info=True)
153
+
154
+ def plugin_path_key(self, filepath: str) -> str:
155
+ """Return the stable, portable key used in the disabled-plugin file."""
156
+ return os.path.relpath(filepath, self.plugin_dir).replace("\\", "/")
157
+
158
+ def is_plugin_disabled(self, filepath: str) -> bool:
159
+ """Return whether *filepath* is disabled in the current settings."""
160
+ return self.plugin_path_key(filepath) in self.disabled_plugins
161
+
121
162
  def open_plugin_folder(self) -> None:
122
163
  """Opens the plugin directory in the OS file explorer."""
123
164
  self.ensure_plugin_dir()
@@ -237,6 +278,7 @@ class PluginManager:
237
278
  self.main_window = parent
238
279
 
239
280
  self.ensure_plugin_dir()
281
+ self.disabled_plugins = self._read_disabled_plugins()
240
282
  self.plugins = []
241
283
  # Clear registries
242
284
  self.menu_actions = []
@@ -276,7 +318,10 @@ class PluginManager:
276
318
  # Module name is the folder name
277
319
  module_name = os.path.basename(root)
278
320
 
279
- self._load_single_plugin(entry_point, module_name, category)
321
+ if self.is_plugin_disabled(entry_point):
322
+ self._register_disabled_plugin(entry_point, module_name, category)
323
+ else:
324
+ self._load_single_plugin(entry_point, module_name, category)
280
325
 
281
326
  else:
282
327
  # === Case 2: Category Folder (Load individual .py files) ===
@@ -290,10 +335,35 @@ class PluginManager:
290
335
  entry_point = os.path.join(root, filename)
291
336
  module_name = os.path.splitext(filename)[0]
292
337
 
293
- self._load_single_plugin(entry_point, module_name, category)
338
+ if self.is_plugin_disabled(entry_point):
339
+ self._register_disabled_plugin(
340
+ entry_point, module_name, category
341
+ )
342
+ else:
343
+ self._load_single_plugin(entry_point, module_name, category)
294
344
 
295
345
  return self.plugins
296
346
 
347
+ def _register_disabled_plugin(
348
+ self, filepath: str, module_name: str, category: str
349
+ ) -> None:
350
+ """Add a disabled plugin without importing or initializing its code."""
351
+ info = self.get_plugin_info_safe(filepath)
352
+ self.plugins.append(
353
+ {
354
+ "name": info.get("name", module_name),
355
+ "version": info.get("version", "Unknown"),
356
+ "author": info.get("author", "Unknown"),
357
+ "description": info.get("description", ""),
358
+ "module": None,
359
+ "category": category or info.get("category", ""),
360
+ "status": "Disabled",
361
+ "filepath": filepath,
362
+ "has_run": False,
363
+ "disabled": True,
364
+ }
365
+ )
366
+
297
367
  def _load_single_plugin(
298
368
  self, filepath: str, module_name: str, category: str
299
369
  ) -> None:
@@ -20,6 +20,7 @@ from PyQt6.QtCore import Qt, QUrl
20
20
  from PyQt6.QtGui import QDesktopServices, QDragEnterEvent, QDropEvent
21
21
  from PyQt6.QtWidgets import (
22
22
  QAbstractItemView,
23
+ QCheckBox,
23
24
  QDialog,
24
25
  QHBoxLayout,
25
26
  QHeaderView,
@@ -42,6 +43,7 @@ class PluginManagerWindow(QDialog):
42
43
  self.btn_remove: Any = None
43
44
  self.table: Any = None
44
45
  self.plugin_manager = plugin_manager
46
+ self._preferences_applied = False
45
47
  self.setWindowTitle("Plugin Manager")
46
48
  self.resize(800, 500)
47
49
  self.setAcceptDrops(True) # Enable drag & drop for the whole window
@@ -53,7 +55,9 @@ class PluginManagerWindow(QDialog):
53
55
  """Build the plugin manager UI with table, buttons, and drag-and-drop support."""
54
56
  layout = QVBoxLayout(self)
55
57
 
56
- lbl_info = QLabel("Drag & Drop .py or .zip files here to install plugins.")
58
+ lbl_info = QLabel(
59
+ "Drag & Drop .py or .zip files here to install plugins. Use the checkbox to enable/disable plugins."
60
+ )
57
61
  lbl_info.setStyleSheet("color: gray; font-style: italic;")
58
62
  layout.addWidget(lbl_info)
59
63
 
@@ -73,6 +77,7 @@ class PluginManagerWindow(QDialog):
73
77
  5, QHeaderView.ResizeMode.Stretch
74
78
  ) # Description stretches
75
79
  self.table.setColumnWidth(1, 200) # Make Name column wider
80
+ self.table.setColumnWidth(0, 120) # Keep the status checkbox on one line
76
81
  self.table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
77
82
  self.table.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
78
83
  self.table.itemSelectionChanged.connect(self.update_button_state)
@@ -117,9 +122,17 @@ class PluginManagerWindow(QDialog):
117
122
 
118
123
  self.table.setRowCount(len(plugins))
119
124
  for row, p in enumerate(plugins):
120
- status_item = QTableWidgetItem(str(p.get("status", "Unknown")))
121
- status_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
125
+ # Keep an empty item for row selection; the checkbox owns visible status text.
126
+ status_item = QTableWidgetItem()
122
127
  self.table.setItem(row, 0, status_item)
128
+
129
+ # Use an explicit widget so macOS keeps the checkbox and status text
130
+ # in a stable horizontal layout instead of stacking the item indicator.
131
+ status_checkbox = QCheckBox(str(p.get("status", "Unknown")))
132
+ status_checkbox.setChecked(not p.get("disabled", False))
133
+ status_checkbox.setToolTip("Enable or disable this plugin")
134
+ status_checkbox.setStyleSheet("QCheckBox { padding-left: 4px; }")
135
+ self.table.setCellWidget(row, 0, status_checkbox)
123
136
  self.table.setItem(row, 1, QTableWidgetItem(str(p.get("name", "Unknown"))))
124
137
  self.table.setItem(row, 2, QTableWidgetItem(str(p.get("version", ""))))
125
138
  self.table.setItem(row, 3, QTableWidgetItem(str(p.get("author", ""))))
@@ -141,15 +154,57 @@ class PluginManagerWindow(QDialog):
141
154
  # Simple color coding for status
142
155
  status = str(p.get("status", ""))
143
156
  color = None
157
+ checkbox_color = None
144
158
  if status.startswith("Error"):
145
159
  color = Qt.GlobalColor.red
160
+ checkbox_color = "red"
146
161
  elif status == "Loaded":
147
162
  color = Qt.GlobalColor.darkGreen
148
- elif status == "No Entry Point":
163
+ checkbox_color = "darkgreen"
164
+ elif status in ("No Entry Point", "Disabled"):
149
165
  color = Qt.GlobalColor.gray
166
+ checkbox_color = "gray"
150
167
 
151
168
  if color:
152
- self.table.item(row, 0).setForeground(color)
169
+ status_checkbox.setStyleSheet(
170
+ f"QCheckBox {{ padding-left: 4px; color: {checkbox_color}; }}"
171
+ )
172
+
173
+ def _status_checkbox(self, row: int) -> Optional[QCheckBox]:
174
+ """Return the visible status checkbox for a plugin table row."""
175
+ checkbox = self.table.cellWidget(row, 0)
176
+ return checkbox if isinstance(checkbox, QCheckBox) else None
177
+
178
+ def _save_checkbox_preferences(self) -> set[str]:
179
+ """Collect and persist checkbox choices."""
180
+ disabled_paths = set()
181
+ for row, plugin in enumerate(self.plugin_manager.plugins):
182
+ status_checkbox = self._status_checkbox(row)
183
+ if status_checkbox is not None and not status_checkbox.isChecked():
184
+ filepath = plugin.get("filepath")
185
+ if filepath:
186
+ disabled_paths.add(self.plugin_manager.plugin_path_key(filepath))
187
+
188
+ self.plugin_manager.save_disabled_plugins(disabled_paths)
189
+ return disabled_paths
190
+
191
+ def _apply_plugin_preferences(self) -> None:
192
+ """Persist checkbox choices and reload plugins."""
193
+ if self._preferences_applied:
194
+ return
195
+ self._preferences_applied = True
196
+
197
+ self._save_checkbox_preferences()
198
+ if self.plugin_manager.main_window:
199
+ self.plugin_manager.discover_plugins(self.plugin_manager.main_window)
200
+ self.plugin_manager.rebuild_plugin_menus()
201
+ else:
202
+ self.plugin_manager.discover_plugins()
203
+
204
+ def done(self, result: int) -> None:
205
+ """Apply plugin preferences for every way the dialog can close."""
206
+ self._apply_plugin_preferences()
207
+ super().done(result)
153
208
 
154
209
  def update_button_state(self) -> None:
155
210
  """Enable or disable the Remove button based on table selection."""
@@ -159,6 +214,7 @@ class PluginManagerWindow(QDialog):
159
214
 
160
215
  def on_reload(self, silent: bool = False) -> None:
161
216
  """Reload all plugins from disk, rebuild main-window UI, and refresh the table."""
217
+ self._save_checkbox_preferences()
162
218
  if self.plugin_manager.main_window:
163
219
  self.plugin_manager.discover_plugins(self.plugin_manager.main_window)
164
220
  self.plugin_manager.rebuild_plugin_menus()
@@ -1663,7 +1663,6 @@ class MainInitManager:
1663
1663
 
1664
1664
  dlg = PluginManagerWindow(self.host.plugin_manager, self.host)
1665
1665
  dlg.exec()
1666
- self.plugin_menu_manager.update_plugin_menu(self.plugin_menu)
1667
1666
 
1668
1667
  manage_plugins_action.triggered.connect(show_plugin_manager)
1669
1668
  self.plugin_menu.addAction(manage_plugins_action)
@@ -428,7 +428,6 @@ class PluginMenuManager:
428
428
 
429
429
  dlg = PluginManagerWindow(self._im.host.plugin_manager, self._im.host)
430
430
  dlg.exec()
431
- self.update_plugin_menu(plugin_menu)
432
431
 
433
432
  def _clear_all_plugin_actions(self) -> None:
434
433
  """Remove all tagged plugin actions from every menu and the export button.