feat: All plugins disabled by default with enable/disable UI
CHANGES: - All plugins now disabled by default (empty enabled list) - Settings > Plugins tab shows all discovered plugins - Checkboxes to enable/disable individual plugins - Enable All / Disable All buttons - Changes take effect immediately (no restart needed for most) - Plugin state saved to config/plugins.json PLUGIN MANAGER: - is_plugin_enabled() - must be explicitly enabled - enable_plugin() - loads and initializes plugin - disable_plugin() - unloads plugin - get_all_discovered_plugins() - returns all available plugins
This commit is contained in:
parent
72c3c132ca
commit
bcd4574b7f
|
|
@ -1,67 +1,60 @@
|
||||||
# 2026-02-13 - EU-Utility Development Session
|
# 2026-02-13 - EU-Utility Major Update
|
||||||
|
|
||||||
## Bug Fixes
|
## UI Fixes Applied
|
||||||
- **Fixed**: `AttributeError: 'dict' object has no attribute 'lower'` in universal_search plugin
|
- **Removed decorative lines** from header (user complaint about "weird lines")
|
||||||
- Location: `plugins/universal_search/plugin.py` line ~501
|
- **Added scroll area** to sidebar - prevents expanding out of screen
|
||||||
- Fix: Changed `'click' in item.lower()` to `'click' in item`
|
- **Added drag functionality** - click and drag header to move window
|
||||||
- Committed: `5e08f56`
|
- **Removed ALL emojis** from all plugins (replaced with text only)
|
||||||
|
|
||||||
## UI Redesign - Spotlight Style
|
## New Features Implemented
|
||||||
- **Complete redesign** of overlay window to match macOS Spotlight aesthetic
|
|
||||||
- Features:
|
|
||||||
- Frosted glass effect with `rgba(40, 40, 40, 180)` background
|
|
||||||
- 20px rounded corners
|
|
||||||
- Drop shadow for depth
|
|
||||||
- Header bar with search icon
|
|
||||||
- Circular plugin icon buttons at bottom (🔍 🧮 🎵 🌐)
|
|
||||||
- Transparent content area for plugins
|
|
||||||
- Committed: `8dbbf4d`
|
|
||||||
|
|
||||||
## Game Integration Features
|
### 1. Resizable Window
|
||||||
|
- Removed `FramelessWindowHint`
|
||||||
|
- Window now fully resizable with minimum size 600x400
|
||||||
|
- Shows in taskbar properly
|
||||||
|
|
||||||
### Floating Icon
|
### 2. Plugin Index Bug Fix
|
||||||
- Draggable floating button (⚡) for in-game access
|
- **Fixed**: Clicking Spotify opened TP Runner instead
|
||||||
- Positioned at top-left (250, 10) near game UI icons
|
- Root cause: Plugin index tracking was incorrect
|
||||||
- EU-styled: dark blue/gray `rgba(20, 25, 35, 220)` with subtle border
|
- Solution: Track buttons in list, store correct index in UserRole data
|
||||||
- Single-click opens overlay, drag to reposition
|
|
||||||
- Hover effect with brighter border
|
|
||||||
- Blue glow effect using QGraphicsDropShadowEffect
|
|
||||||
- Committed: `b007927`
|
|
||||||
|
|
||||||
### Game Reader Plugin (OCR)
|
### 3. Skill Scanner v2.0 (Complete Rewrite)
|
||||||
- New plugin for reading in-game menus and text
|
**OCR Features:**
|
||||||
- Hotkey: `Ctrl+Shift+R`
|
- Screenshot capture of skills window
|
||||||
- Features:
|
- EasyOCR/Pytesseract fallback
|
||||||
- Screen capture with OCR (EasyOCR or Tesseract)
|
- Parses skill names, ranks, points
|
||||||
- Extract text from NPC dialogue, missions, prices
|
|
||||||
- Copy captured text to clipboard
|
|
||||||
- Status feedback (capturing/error/success)
|
|
||||||
- Location: `plugins/game_reader/plugin.py`
|
|
||||||
- Dependencies: `pip install easyocr` or `pip install pytesseract`
|
|
||||||
- Committed: `d74de07`
|
|
||||||
|
|
||||||
## User Feedback - Game Screenshots
|
**Log Watching:**
|
||||||
User shared screenshots showing ideal use cases:
|
- Real-time chat.log monitoring
|
||||||
1. **Trade Terminal** - Price checking, category browsing
|
- Automatic skill gain detection
|
||||||
2. **Inventory** - Item management, PED balance (26.02 PED), weight tracking
|
- Patterns: "Aim has improved by 5.2 points", "You gained 10 points in Rifle"
|
||||||
3. **Auction** - Price tracking, markup analysis, bid monitoring
|
- Automatically adds gains to total values
|
||||||
|
- Real-time gain display in UI
|
||||||
|
|
||||||
## All Hotkeys
|
### 4. Profession Scanner (NEW PLUGIN)
|
||||||
| Hotkey | Action |
|
- OCR scan of professions window
|
||||||
|--------|--------|
|
- Tracks profession ranks (Elite, Champion, etc.)
|
||||||
| `Ctrl+Shift+U` | Toggle overlay |
|
- Progress percentage with visual bars
|
||||||
| `Ctrl+Shift+F` | Universal Search |
|
- 21st plugin added to EU-Utility
|
||||||
| `Ctrl+Shift+C` | Calculator |
|
|
||||||
| `Ctrl+Shift+M` | Spotify |
|
|
||||||
| `Ctrl+Shift+R` | Game Reader (OCR) |
|
|
||||||
|
|
||||||
## Repository
|
### 5. Customizable Dashboard
|
||||||
- EU-Utility: `https://git.lemonlink.eu/impulsivefps/EU-Utility`
|
- Click "Customize" to select widgets
|
||||||
- Local: `/home/impulsivefps/.openclaw/workspace/projects/EU-Utility/`
|
- Available widgets:
|
||||||
- Run: `python -m core.main` (from projects/EU-Utility/)
|
- PED Balance
|
||||||
|
- Skills Tracked
|
||||||
|
- Inventory Items
|
||||||
|
- Current DPP
|
||||||
|
- Today's Skill Gains
|
||||||
|
- Professions Count
|
||||||
|
- Active Missions
|
||||||
|
- Codex Progress
|
||||||
|
- Globals/HOFs
|
||||||
|
- Session Time
|
||||||
|
- Auto-refresh every 5 seconds
|
||||||
|
- Reads data from all plugin JSON files
|
||||||
|
|
||||||
## Next Ideas (From User)
|
## Commits Made
|
||||||
- Auction Price Tracker - capture listings, track history, price alerts
|
- `5b127cf` - UI fixes (drag, scroll, no emojis)
|
||||||
- Inventory Value Calculator - total TT value, market price comparison
|
- `72c3c13` - Resizable window, OCR scanners, customizable dashboard
|
||||||
- Auto-Capture Mode - detect window type, extract relevant data automatically
|
|
||||||
- Better OCR region selection for specific windows
|
## Total Plugins: 21
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,8 @@ class PluginManager:
|
||||||
return json.loads(config_path.read_text())
|
return json.loads(config_path.read_text())
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
pass
|
pass
|
||||||
return {"enabled": [], "disabled": [], "settings": {}}
|
# Default: no plugins enabled - all disabled by default
|
||||||
|
return {"enabled": [], "settings": {}}
|
||||||
|
|
||||||
def save_config(self) -> None:
|
def save_config(self) -> None:
|
||||||
"""Save plugin configuration."""
|
"""Save plugin configuration."""
|
||||||
|
|
@ -51,6 +52,40 @@ class PluginManager:
|
||||||
config_path.parent.mkdir(parents=True, exist_ok=True)
|
config_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
config_path.write_text(json.dumps(self.config, indent=2))
|
config_path.write_text(json.dumps(self.config, indent=2))
|
||||||
|
|
||||||
|
def is_plugin_enabled(self, plugin_id: str) -> bool:
|
||||||
|
"""Check if a plugin is enabled."""
|
||||||
|
# Must be explicitly enabled - default is disabled
|
||||||
|
return plugin_id in self.config.get("enabled", [])
|
||||||
|
|
||||||
|
def enable_plugin(self, plugin_id: str) -> bool:
|
||||||
|
"""Enable a plugin and load it."""
|
||||||
|
if plugin_id in self.config.get("enabled", []):
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Add to enabled list
|
||||||
|
if "enabled" not in self.config:
|
||||||
|
self.config["enabled"] = []
|
||||||
|
self.config["enabled"].append(plugin_id)
|
||||||
|
self.save_config()
|
||||||
|
|
||||||
|
# Load the plugin
|
||||||
|
plugin_class = self.plugin_classes.get(plugin_id)
|
||||||
|
if plugin_class:
|
||||||
|
return self.load_plugin(plugin_class)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def disable_plugin(self, plugin_id: str) -> bool:
|
||||||
|
"""Disable a plugin and unload it."""
|
||||||
|
if "enabled" in self.config and plugin_id in self.config["enabled"]:
|
||||||
|
self.config["enabled"].remove(plugin_id)
|
||||||
|
self.save_config()
|
||||||
|
|
||||||
|
# Unload if loaded
|
||||||
|
if plugin_id in self.plugins:
|
||||||
|
self.unload_plugin(plugin_id)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def discover_plugins(self) -> List[Type[BasePlugin]]:
|
def discover_plugins(self) -> List[Type[BasePlugin]]:
|
||||||
"""Discover all available plugin classes with error handling."""
|
"""Discover all available plugin classes with error handling."""
|
||||||
discovered = []
|
discovered = []
|
||||||
|
|
@ -153,18 +188,23 @@ class PluginManager:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def load_all_plugins(self) -> None:
|
def load_all_plugins(self) -> None:
|
||||||
"""Discover and load all available plugins."""
|
"""Load only enabled plugins."""
|
||||||
discovered = self.discover_plugins()
|
discovered = self.discover_plugins()
|
||||||
|
|
||||||
for plugin_class in discovered:
|
for plugin_class in discovered:
|
||||||
plugin_id = f"{plugin_class.__module__}.{plugin_class.__name__}"
|
plugin_id = f"{plugin_class.__module__}.{plugin_class.__name__}"
|
||||||
|
|
||||||
# Check if disabled
|
# Only load if explicitly enabled
|
||||||
if plugin_id in self.config.get("disabled", []):
|
if self.is_plugin_enabled(plugin_id):
|
||||||
print(f"Skipping disabled plugin: {plugin_class.name}")
|
self.load_plugin(plugin_class)
|
||||||
continue
|
else:
|
||||||
|
# Just store class reference but don't load
|
||||||
self.load_plugin(plugin_class)
|
self.plugin_classes[plugin_id] = plugin_class
|
||||||
|
print(f"[PluginManager] Plugin available (disabled): {plugin_class.name}")
|
||||||
|
|
||||||
|
def get_all_discovered_plugins(self) -> Dict[str, type]:
|
||||||
|
"""Get all discovered plugin classes (including disabled)."""
|
||||||
|
return self.plugin_classes.copy()
|
||||||
|
|
||||||
def get_plugin(self, plugin_id: str) -> Optional[BasePlugin]:
|
def get_plugin(self, plugin_id: str) -> Optional[BasePlugin]:
|
||||||
"""Get a loaded plugin by ID."""
|
"""Get a loaded plugin by ID."""
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,9 @@ from PyQt6.QtWidgets import (
|
||||||
QWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
QWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
||||||
QPushButton, QCheckBox, QLineEdit, QComboBox,
|
QPushButton, QCheckBox, QLineEdit, QComboBox,
|
||||||
QSlider, QTabWidget, QGroupBox, QListWidget,
|
QSlider, QTabWidget, QGroupBox, QListWidget,
|
||||||
QListWidgetItem, QFrame, QFileDialog
|
QListWidgetItem, QFrame, QFileDialog, QScrollArea
|
||||||
)
|
)
|
||||||
from PyQt6.QtCore import Qt
|
from PyQt6.QtCore import Qt, QTimer
|
||||||
|
|
||||||
from core.settings import get_settings
|
from core.settings import get_settings
|
||||||
from plugins.base_plugin import BasePlugin
|
from plugins.base_plugin import BasePlugin
|
||||||
|
|
@ -38,7 +38,7 @@ class SettingsPlugin(BasePlugin):
|
||||||
layout.setContentsMargins(0, 0, 0, 0)
|
layout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
|
||||||
# Title
|
# Title
|
||||||
title = QLabel("⚙️ Settings")
|
title = QLabel("Settings")
|
||||||
title.setStyleSheet("color: white; font-size: 16px; font-weight: bold;")
|
title.setStyleSheet("color: white; font-size: 16px; font-weight: bold;")
|
||||||
layout.addWidget(title)
|
layout.addWidget(title)
|
||||||
|
|
||||||
|
|
@ -84,7 +84,7 @@ class SettingsPlugin(BasePlugin):
|
||||||
# Save/Reset buttons
|
# Save/Reset buttons
|
||||||
btn_layout = QHBoxLayout()
|
btn_layout = QHBoxLayout()
|
||||||
|
|
||||||
save_btn = QPushButton("💾 Save Settings")
|
save_btn = QPushButton("Save Settings")
|
||||||
save_btn.setStyleSheet("""
|
save_btn.setStyleSheet("""
|
||||||
QPushButton {
|
QPushButton {
|
||||||
background-color: #4caf50;
|
background-color: #4caf50;
|
||||||
|
|
@ -98,7 +98,7 @@ class SettingsPlugin(BasePlugin):
|
||||||
save_btn.clicked.connect(self._save_settings)
|
save_btn.clicked.connect(self._save_settings)
|
||||||
btn_layout.addWidget(save_btn)
|
btn_layout.addWidget(save_btn)
|
||||||
|
|
||||||
reset_btn = QPushButton("↺ Reset to Default")
|
reset_btn = QPushButton("Reset to Default")
|
||||||
reset_btn.setStyleSheet("""
|
reset_btn.setStyleSheet("""
|
||||||
QPushButton {
|
QPushButton {
|
||||||
background-color: rgba(255,255,255,20);
|
background-color: rgba(255,255,255,20);
|
||||||
|
|
@ -184,68 +184,116 @@ class SettingsPlugin(BasePlugin):
|
||||||
return tab
|
return tab
|
||||||
|
|
||||||
def _create_plugins_tab(self):
|
def _create_plugins_tab(self):
|
||||||
"""Create plugins management tab."""
|
"""Create plugins management tab - enable/disable plugins."""
|
||||||
tab = QWidget()
|
tab = QWidget()
|
||||||
layout = QVBoxLayout(tab)
|
layout = QVBoxLayout(tab)
|
||||||
layout.setSpacing(15)
|
layout.setSpacing(15)
|
||||||
|
|
||||||
# Installed plugins
|
# Info label
|
||||||
plugins_group = QGroupBox("Installed Plugins")
|
info = QLabel("Check plugins to enable them. Uncheck to disable. Changes take effect immediately.")
|
||||||
plugins_group.setStyleSheet(self._group_style())
|
info.setStyleSheet("color: rgba(255,255,255,150);")
|
||||||
plugins_layout = QVBoxLayout(plugins_group)
|
layout.addWidget(info)
|
||||||
|
|
||||||
self.plugins_list = QListWidget()
|
# Scroll area for plugin list
|
||||||
self.plugins_list.setStyleSheet("""
|
scroll = QScrollArea()
|
||||||
QListWidget {
|
scroll.setWidgetResizable(True)
|
||||||
background-color: rgba(30, 35, 45, 200);
|
scroll.setFrameShape(QFrame.Shape.NoFrame)
|
||||||
color: white;
|
scroll.setStyleSheet("background: transparent; border: none;")
|
||||||
border: 1px solid rgba(100, 110, 130, 80);
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
QListWidget::item {
|
|
||||||
padding: 8px;
|
|
||||||
border-bottom: 1px solid rgba(100, 110, 130, 40);
|
|
||||||
}
|
|
||||||
QListWidget::item:selected {
|
|
||||||
background-color: rgba(255, 140, 66, 100);
|
|
||||||
}
|
|
||||||
""")
|
|
||||||
|
|
||||||
# Add sample plugins
|
scroll_content = QWidget()
|
||||||
plugins = [
|
plugins_layout = QVBoxLayout(scroll_content)
|
||||||
("Universal Search", True),
|
plugins_layout.setSpacing(8)
|
||||||
("Calculator", True),
|
plugins_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
||||||
("Spotify", True),
|
|
||||||
("Skill Scanner", True),
|
|
||||||
("Loot Tracker", True),
|
|
||||||
("Mining Helper", False),
|
|
||||||
("Chat Logger", True),
|
|
||||||
]
|
|
||||||
|
|
||||||
for name, enabled in plugins:
|
self.plugin_checkboxes = {}
|
||||||
item = QListWidgetItem(f"{'✓' if enabled else '✗'} {name}")
|
|
||||||
item.setFlags(item.flags() | Qt.ItemFlag.ItemIsUserCheckable)
|
|
||||||
item.setCheckState(Qt.CheckState.Checked if enabled else Qt.CheckState.Unchecked)
|
|
||||||
self.plugins_list.addItem(item)
|
|
||||||
|
|
||||||
plugins_layout.addWidget(self.plugins_list)
|
# Get all discovered plugins from plugin manager
|
||||||
|
if hasattr(self.overlay, 'plugin_manager'):
|
||||||
|
plugin_manager = self.overlay.plugin_manager
|
||||||
|
all_plugins = plugin_manager.get_all_discovered_plugins()
|
||||||
|
|
||||||
|
# Sort by name
|
||||||
|
sorted_plugins = sorted(all_plugins.items(), key=lambda x: x[1].name)
|
||||||
|
|
||||||
|
for plugin_id, plugin_class in sorted_plugins:
|
||||||
|
row = QHBoxLayout()
|
||||||
|
|
||||||
|
# Checkbox
|
||||||
|
cb = QCheckBox(plugin_class.name)
|
||||||
|
cb.setChecked(plugin_manager.is_plugin_enabled(plugin_id))
|
||||||
|
cb.setStyleSheet("""
|
||||||
|
QCheckBox {
|
||||||
|
color: white;
|
||||||
|
spacing: 8px;
|
||||||
|
}
|
||||||
|
QCheckBox::indicator {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
# Connect to enable/disable
|
||||||
|
cb.stateChanged.connect(
|
||||||
|
lambda state, pid=plugin_id: self._toggle_plugin(pid, state == Qt.CheckState.Checked.value)
|
||||||
|
)
|
||||||
|
self.plugin_checkboxes[plugin_id] = cb
|
||||||
|
row.addWidget(cb)
|
||||||
|
|
||||||
|
# Version
|
||||||
|
version_label = QLabel(f"v{plugin_class.version}")
|
||||||
|
version_label.setStyleSheet("color: rgba(255,255,255,100); font-size: 11px;")
|
||||||
|
row.addWidget(version_label)
|
||||||
|
|
||||||
|
# Description
|
||||||
|
desc_label = QLabel(f"- {plugin_class.description}")
|
||||||
|
desc_label.setStyleSheet("color: rgba(255,255,255,100); font-size: 11px;")
|
||||||
|
desc_label.setWordWrap(True)
|
||||||
|
row.addWidget(desc_label, 1)
|
||||||
|
|
||||||
|
row.addStretch()
|
||||||
|
plugins_layout.addLayout(row)
|
||||||
|
|
||||||
|
# Separator
|
||||||
|
sep = QFrame()
|
||||||
|
sep.setFrameShape(QFrame.Shape.HLine)
|
||||||
|
sep.setStyleSheet("background-color: rgba(100, 110, 130, 40);")
|
||||||
|
sep.setFixedHeight(1)
|
||||||
|
plugins_layout.addWidget(sep)
|
||||||
|
|
||||||
# Plugin store button
|
plugins_layout.addStretch()
|
||||||
store_btn = QPushButton("🛒 Open Plugin Store")
|
scroll.setWidget(scroll_content)
|
||||||
store_btn.setStyleSheet("""
|
layout.addWidget(scroll)
|
||||||
|
|
||||||
|
# Buttons
|
||||||
|
btn_layout = QHBoxLayout()
|
||||||
|
|
||||||
|
enable_all_btn = QPushButton("Enable All")
|
||||||
|
enable_all_btn.setStyleSheet("""
|
||||||
QPushButton {
|
QPushButton {
|
||||||
background-color: #ff8c42;
|
background-color: #4caf50;
|
||||||
color: white;
|
color: white;
|
||||||
padding: 10px;
|
padding: 8px 16px;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
font-weight: bold;
|
|
||||||
}
|
}
|
||||||
""")
|
""")
|
||||||
plugins_layout.addWidget(store_btn)
|
enable_all_btn.clicked.connect(self._enable_all_plugins)
|
||||||
|
btn_layout.addWidget(enable_all_btn)
|
||||||
|
|
||||||
layout.addWidget(plugins_group)
|
disable_all_btn = QPushButton("Disable All")
|
||||||
layout.addStretch()
|
disable_all_btn.setStyleSheet("""
|
||||||
|
QPushButton {
|
||||||
|
background-color: rgba(255,255,255,20);
|
||||||
|
color: white;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
disable_all_btn.clicked.connect(self._disable_all_plugins)
|
||||||
|
btn_layout.addWidget(disable_all_btn)
|
||||||
|
|
||||||
|
btn_layout.addStretch()
|
||||||
|
layout.addLayout(btn_layout)
|
||||||
|
|
||||||
return tab
|
return tab
|
||||||
|
|
||||||
|
|
@ -470,3 +518,44 @@ class SettingsPlugin(BasePlugin):
|
||||||
def _clear_data(self):
|
def _clear_data(self):
|
||||||
"""Clear all data."""
|
"""Clear all data."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def _toggle_plugin(self, plugin_id: str, enable: bool):
|
||||||
|
"""Enable or disable a plugin."""
|
||||||
|
if not hasattr(self.overlay, 'plugin_manager'):
|
||||||
|
return
|
||||||
|
|
||||||
|
plugin_manager = self.overlay.plugin_manager
|
||||||
|
|
||||||
|
if enable:
|
||||||
|
success = plugin_manager.enable_plugin(plugin_id)
|
||||||
|
if success:
|
||||||
|
print(f"[Settings] Enabled plugin: {plugin_id}")
|
||||||
|
else:
|
||||||
|
print(f"[Settings] Failed to enable plugin: {plugin_id}")
|
||||||
|
else:
|
||||||
|
success = plugin_manager.disable_plugin(plugin_id)
|
||||||
|
if success:
|
||||||
|
print(f"[Settings] Disabled plugin: {plugin_id}")
|
||||||
|
|
||||||
|
# Notify user a restart might be needed for some changes
|
||||||
|
# But we try to apply immediately
|
||||||
|
|
||||||
|
def _enable_all_plugins(self):
|
||||||
|
"""Enable all plugins."""
|
||||||
|
if not hasattr(self.overlay, 'plugin_manager'):
|
||||||
|
return
|
||||||
|
|
||||||
|
plugin_manager = self.overlay.plugin_manager
|
||||||
|
for plugin_id, cb in self.plugin_checkboxes.items():
|
||||||
|
cb.setChecked(True)
|
||||||
|
plugin_manager.enable_plugin(plugin_id)
|
||||||
|
|
||||||
|
def _disable_all_plugins(self):
|
||||||
|
"""Disable all plugins."""
|
||||||
|
if not hasattr(self.overlay, 'plugin_manager'):
|
||||||
|
return
|
||||||
|
|
||||||
|
plugin_manager = self.overlay.plugin_manager
|
||||||
|
for plugin_id, cb in self.plugin_checkboxes.items():
|
||||||
|
cb.setChecked(False)
|
||||||
|
plugin_manager.disable_plugin(plugin_id)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue