From 6a54e9945266a9fcb336d9ed65cd9c13bdb0e530 Mon Sep 17 00:00:00 2001 From: LemonNexus Date: Sun, 15 Feb 2026 02:09:05 +0000 Subject: [PATCH] cleanup: Remove plugins now built into core framework MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The following plugins are now built into EU-Utility core: - settings → Built-in Settings tab - plugin_store_ui → Built-in Plugin Store - dashboard → Built-in Dashboard with plugin hooks - universal_search → Built-in Universal Search - import_export → Built-in Backup/Restore - auto_updater → Built-in Auto-Updater (disabled by default) These features are now framework components, not optional plugins. Users get them automatically with the core application. Remaining plugins (17): - Tools: calculator, crafting_calc, enhancer_calc, dpp_calculator - Tracking: skill_scanner, loot_tracker, mining_helper, mission_tracker, codex_tracker, global_tracker - Information: nexus_search, chat_logger - Market: auction_tracker, inventory_manager - Analytics: analytics - Media: spotify_controller - Social: discord_presence - Navigation: tp_runner --- manifest.json | 48 - plugins/analytics/__init__.py | 3 - .../__pycache__/__init__.cpython-312.pyc | Bin 203 -> 0 bytes .../__pycache__/plugin.cpython-312.pyc | Bin 26052 -> 0 bytes plugins/analytics/plugin.py | 525 -------- plugins/auto_updater/__init__.py | 3 - .../__pycache__/__init__.cpython-312.pyc | Bin 208 -> 0 bytes .../__pycache__/plugin.cpython-312.pyc | Bin 25950 -> 0 bytes plugins/auto_updater/plugin.py | 481 ------- plugins/dashboard/__init__.py | 7 - .../__pycache__/__init__.cpython-312.pyc | Bin 243 -> 0 bytes .../__pycache__/plugin.cpython-312.pyc | Bin 15962 -> 0 bytes plugins/dashboard/plugin.py | 326 ----- plugins/import_export/__init__.py | 3 - .../__pycache__/__init__.cpython-312.pyc | Bin 210 -> 0 bytes .../__pycache__/plugin.cpython-312.pyc | Bin 16792 -> 0 bytes plugins/import_export/plugin.py | 333 ----- plugins/plugin_store_ui/__init__.py | 7 - .../__pycache__/__init__.cpython-312.pyc | Bin 259 -> 0 bytes .../__pycache__/plugin.cpython-312.pyc | Bin 11821 -> 0 bytes plugins/plugin_store_ui/plugin.py | 273 ---- plugins/settings/__init__.py | 7 - .../__pycache__/__init__.cpython-312.pyc | Bin 240 -> 0 bytes .../__pycache__/plugin.cpython-312.pyc | Bin 52393 -> 0 bytes plugins/settings/plugin.py | 1174 ----------------- plugins/universal_search/__init__.py | 7 - .../__pycache__/__init__.cpython-312.pyc | Bin 278 -> 0 bytes .../__pycache__/plugin.cpython-312.pyc | Bin 26627 -> 0 bytes plugins/universal_search/plugin.py | 600 --------- 29 files changed, 3797 deletions(-) delete mode 100644 plugins/analytics/__init__.py delete mode 100644 plugins/analytics/__pycache__/__init__.cpython-312.pyc delete mode 100644 plugins/analytics/__pycache__/plugin.cpython-312.pyc delete mode 100644 plugins/analytics/plugin.py delete mode 100644 plugins/auto_updater/__init__.py delete mode 100644 plugins/auto_updater/__pycache__/__init__.cpython-312.pyc delete mode 100644 plugins/auto_updater/__pycache__/plugin.cpython-312.pyc delete mode 100644 plugins/auto_updater/plugin.py delete mode 100644 plugins/dashboard/__init__.py delete mode 100644 plugins/dashboard/__pycache__/__init__.cpython-312.pyc delete mode 100644 plugins/dashboard/__pycache__/plugin.cpython-312.pyc delete mode 100644 plugins/dashboard/plugin.py delete mode 100644 plugins/import_export/__init__.py delete mode 100644 plugins/import_export/__pycache__/__init__.cpython-312.pyc delete mode 100644 plugins/import_export/__pycache__/plugin.cpython-312.pyc delete mode 100644 plugins/import_export/plugin.py delete mode 100644 plugins/plugin_store_ui/__init__.py delete mode 100644 plugins/plugin_store_ui/__pycache__/__init__.cpython-312.pyc delete mode 100644 plugins/plugin_store_ui/__pycache__/plugin.cpython-312.pyc delete mode 100644 plugins/plugin_store_ui/plugin.py delete mode 100644 plugins/settings/__init__.py delete mode 100644 plugins/settings/__pycache__/__init__.cpython-312.pyc delete mode 100644 plugins/settings/__pycache__/plugin.cpython-312.pyc delete mode 100644 plugins/settings/plugin.py delete mode 100644 plugins/universal_search/__init__.py delete mode 100644 plugins/universal_search/__pycache__/__init__.cpython-312.pyc delete mode 100644 plugins/universal_search/__pycache__/plugin.cpython-312.pyc delete mode 100644 plugins/universal_search/plugin.py diff --git a/manifest.json b/manifest.json index 2333f46..f39d2a3 100644 --- a/manifest.json +++ b/manifest.json @@ -52,22 +52,6 @@ "min_core_version": "2.0.0", "category": "Information" }, - { - "id": "dashboard", - "name": "Dashboard", - "version": "1.0.0", - "author": "ImpulsiveFPS", - "description": "Overview dashboard showing session stats, recent globals, skill gains, and quick access to all plugins.", - "folder": "plugins/dashboard/", - "icon": "grid", - "tags": ["dashboard", "overview", "stats"], - "dependencies": { - "core": ["log"], - "plugins": [] - }, - "min_core_version": "2.0.0", - "category": "Information" - }, { "id": "loot_tracker", "name": "Loot Tracker", @@ -307,38 +291,6 @@ }, "min_core_version": "2.0.0", "category": "Tools" - }, - { - "id": "universal_search", - "name": "Universal Search", - "version": "1.0.0", - "author": "ImpulsiveFPS", - "description": "Quick search across all plugins. Find items, skills, locations instantly.", - "folder": "plugins/universal_search/", - "icon": "search", - "tags": ["search", "quick", "universal"], - "dependencies": { - "core": [], - "plugins": [] - }, - "min_core_version": "2.0.0", - "category": "Tools" - }, - { - "id": "import_export", - "name": "Import/Export", - "version": "1.0.0", - "author": "ImpulsiveFPS", - "description": "Import and export your EU-Utility data. Backup and restore functionality.", - "folder": "plugins/import_export/", - "icon": "external", - "tags": ["import", "export", "backup", "data"], - "dependencies": { - "core": ["data_store"], - "plugins": [] - }, - "min_core_version": "2.0.0", - "category": "Data" } ] } diff --git a/plugins/analytics/__init__.py b/plugins/analytics/__init__.py deleted file mode 100644 index 76dab5e..0000000 --- a/plugins/analytics/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .plugin import AnalyticsPlugin - -__all__ = ['AnalyticsPlugin'] diff --git a/plugins/analytics/__pycache__/__init__.cpython-312.pyc b/plugins/analytics/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index d5af77a9a997d01f06497b33405834df64578d13..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 203 zcmX@j%ge<81aF`8XF3Dv#~=<2FhLog#ej_I3@HpLj5!Rsj8Tk?3@J?Mj8ROL%$h7O z8G(|TjJNn5^Ad9^OEQy-19D2!GxLg=f#QCe%(vJI5d2&0@$rc{Iq~r;89svy|0N5N zEY?qi>DG^r&&& KBYP1$P!Ir^?lR~A diff --git a/plugins/analytics/__pycache__/plugin.cpython-312.pyc b/plugins/analytics/__pycache__/plugin.cpython-312.pyc deleted file mode 100644 index 222bbb883884285f5a5632b92ed8351cd64cdb74..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26052 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 Z%K3Au00N)dqYhKi)#<;Z@Rxk&e*?G=$O8ZX diff --git a/plugins/analytics/plugin.py b/plugins/analytics/plugin.py deleted file mode 100644 index e8491bb..0000000 --- a/plugins/analytics/plugin.py +++ /dev/null @@ -1,525 +0,0 @@ -# Description: Analytics and monitoring system for EU-Utility -# Privacy-focused usage tracking and performance monitoring - -""" -EU-Utility Analytics System - -Privacy-focused analytics with opt-in tracking: -- Feature usage statistics -- Performance monitoring (FPS, memory, CPU) -- Error reporting -- Health checks - -All data is stored locally by default. -""" - -from PyQt6.QtWidgets import ( - QWidget, QVBoxLayout, QHBoxLayout, QLabel, - QPushButton, QCheckBox, QProgressBar, QTableWidget, - QTableWidgetItem, QTabWidget, QGroupBox -) -from PyQt6.QtCore import QTimer, pyqtSignal, Qt -from plugins.base_plugin import BasePlugin -from datetime import datetime, timedelta -import json -import os -import psutil -import time - - -class AnalyticsPlugin(BasePlugin): - """ - Analytics and monitoring dashboard for EU-Utility. - - Tracks: - - Feature usage (opt-in) - - System performance - - Error occurrences - - Health status - """ - - name = "Analytics" - version = "1.0.0" - author = "LemonNexus" - description = "Usage analytics and performance monitoring" - icon = "bar-chart" - - def initialize(self): - """Initialize analytics system.""" - # Load settings - self.enabled = self.load_data("enabled", False) - self.track_performance = self.load_data("track_performance", True) - self.track_usage = self.load_data("track_usage", False) - - # Data storage - self.usage_data = self.load_data("usage", {}) - self.performance_data = self.load_data("performance", []) - self.error_data = self.load_data("errors", []) - - # Performance tracking - self.start_time = time.time() - self.session_events = [] - - # Setup timers - if self.track_performance: - self._setup_performance_monitoring() - - def _setup_performance_monitoring(self): - """Setup periodic performance monitoring.""" - self.performance_timer = QTimer() - self.performance_timer.timeout.connect(self._record_performance) - self.performance_timer.start(30000) # Every 30 seconds - - # Initial record - self._record_performance() - - def _record_performance(self): - """Record current system performance.""" - try: - # Get system stats - cpu_percent = psutil.cpu_percent(interval=1) - memory = psutil.virtual_memory() - - # Get process info - process = psutil.Process() - process_memory = process.memory_info().rss / 1024 / 1024 # MB - - record = { - 'timestamp': datetime.now().isoformat(), - 'cpu_percent': cpu_percent, - 'memory_percent': memory.percent, - 'memory_used_mb': process_memory, - 'uptime_seconds': time.time() - self.start_time - } - - self.performance_data.append(record) - - # Keep only last 1000 records - if len(self.performance_data) > 1000: - self.performance_data = self.performance_data[-1000:] - - self.save_data("performance", self.performance_data) - - except Exception as e: - self.log_error(f"Performance recording failed: {e}") - - def record_event(self, event_type, details=None): - """Record a usage event (if tracking enabled).""" - if not self.track_usage: - return - - event = { - 'type': event_type, - 'timestamp': datetime.now().isoformat(), - 'details': details or {} - } - - self.session_events.append(event) - - # Update usage stats - if event_type not in self.usage_data: - self.usage_data[event_type] = {'count': 0, 'last_used': None} - - self.usage_data[event_type]['count'] += 1 - self.usage_data[event_type]['last_used'] = datetime.now().isoformat() - self.save_data("usage", self.usage_data) - - def record_error(self, error_message, context=None): - """Record an error occurrence.""" - error = { - 'message': str(error_message), - 'timestamp': datetime.now().isoformat(), - 'context': context or {}, - 'session_uptime': time.time() - self.start_time - } - - self.error_data.append(error) - - # Keep only last 100 errors - if len(self.error_data) > 100: - self.error_data = self.error_data[-100:] - - self.save_data("errors", self.error_data) - - def get_ui(self): - """Create analytics dashboard UI.""" - widget = QWidget() - layout = QVBoxLayout(widget) - layout.setSpacing(15) - - # Title - title = QLabel("Analytics & Monitoring") - title.setStyleSheet("font-size: 20px; font-weight: bold; color: #4a9eff;") - layout.addWidget(title) - - # Tabs - tabs = QTabWidget() - - # Overview tab - tabs.addTab(self._create_overview_tab(), "Overview") - - # Performance tab - tabs.addTab(self._create_performance_tab(), "Performance") - - # Usage tab - tabs.addTab(self._create_usage_tab(), "Usage") - - # Errors tab - tabs.addTab(self._create_errors_tab(), "Errors") - - # Settings tab - tabs.addTab(self._create_settings_tab(), "Settings") - - layout.addWidget(tabs) - - # Refresh button - refresh_btn = QPushButton("Refresh Data") - refresh_btn.clicked.connect(self._refresh_all) - layout.addWidget(refresh_btn) - - return widget - - def _create_overview_tab(self): - """Create overview dashboard.""" - tab = QWidget() - layout = QVBoxLayout(tab) - - # System health - health_group = QGroupBox("System Health") - health_layout = QVBoxLayout(health_group) - - self.health_status = QLabel("Checking...") - self.health_status.setStyleSheet("font-size: 16px; font-weight: bold;") - health_layout.addWidget(self.health_status) - - # Health metrics - self.cpu_label = QLabel("CPU: --") - self.memory_label = QLabel("Memory: --") - self.uptime_label = QLabel("Uptime: --") - - health_layout.addWidget(self.cpu_label) - health_layout.addWidget(self.memory_label) - health_layout.addWidget(self.uptime_label) - - layout.addWidget(health_group) - - # Session stats - stats_group = QGroupBox("Session Statistics") - stats_layout = QVBoxLayout(stats_group) - - self.events_label = QLabel(f"Events recorded: {len(self.session_events)}") - self.errors_label = QLabel(f"Errors: {len(self.error_data)}") - self.plugins_label = QLabel(f"Active plugins: --") - - stats_layout.addWidget(self.events_label) - stats_layout.addWidget(self.errors_label) - stats_layout.addWidget(self.plugins_label) - - layout.addWidget(stats_group) - - layout.addStretch() - - # Update immediately - self._update_overview() - - return tab - - def _create_performance_tab(self): - """Create performance monitoring tab.""" - tab = QWidget() - layout = QVBoxLayout(tab) - - # Current stats - current_group = QGroupBox("Current Performance") - current_layout = QVBoxLayout(current_group) - - self.perf_cpu = QLabel("CPU Usage: --") - self.perf_memory = QLabel("Memory Usage: --") - self.perf_process = QLabel("Process Memory: --") - - current_layout.addWidget(self.perf_cpu) - current_layout.addWidget(self.perf_memory) - current_layout.addWidget(self.perf_process) - - layout.addWidget(current_group) - - # Historical data - history_group = QGroupBox("Performance History (Last Hour)") - history_layout = QVBoxLayout(history_group) - - self.perf_table = QTableWidget() - self.perf_table.setColumnCount(4) - self.perf_table.setHorizontalHeaderLabels(["Time", "CPU %", "Memory %", "Process MB"]) - self.perf_table.horizontalHeader().setStretchLastSection(True) - - history_layout.addWidget(self.perf_table) - layout.addWidget(history_group) - - layout.addStretch() - - self._update_performance_tab() - - return tab - - def _create_usage_tab(self): - """Create usage statistics tab.""" - tab = QWidget() - layout = QVBoxLayout(tab) - - # Usage table - self.usage_table = QTableWidget() - self.usage_table.setColumnCount(3) - self.usage_table.setHorizontalHeaderLabels(["Feature", "Usage Count", "Last Used"]) - self.usage_table.horizontalHeader().setStretchLastSection(True) - - layout.addWidget(self.usage_table) - - # Update data - self._update_usage_tab() - - return tab - - def _create_errors_tab(self): - """Create error log tab.""" - tab = QWidget() - layout = QVBoxLayout(tab) - - # Error table - self.error_table = QTableWidget() - self.error_table.setColumnCount(3) - self.error_table.setHorizontalHeaderLabels(["Time", "Error", "Context"]) - self.error_table.horizontalHeader().setStretchLastSection(True) - - layout.addWidget(self.error_table) - - # Clear button - clear_btn = QPushButton("Clear Error Log") - clear_btn.clicked.connect(self._clear_errors) - layout.addWidget(clear_btn) - - self._update_errors_tab() - - return tab - - def _create_settings_tab(self): - """Create analytics settings tab.""" - tab = QWidget() - layout = QVBoxLayout(tab) - - # Privacy notice - privacy = QLabel( - "🔒 Privacy Notice:\n" - "All analytics data is stored locally on your machine. " - "No data is sent to external servers unless you explicitly configure it." - ) - privacy.setStyleSheet("background-color: #2a3a40; padding: 10px; border-radius: 4px;") - privacy.setWordWrap(True) - layout.addWidget(privacy) - - # Enable analytics - self.enable_checkbox = QCheckBox("Enable Analytics") - self.enable_checkbox.setChecked(self.enabled) - self.enable_checkbox.toggled.connect(self._on_enable_changed) - layout.addWidget(self.enable_checkbox) - - # Performance tracking - self.perf_checkbox = QCheckBox("Track System Performance") - self.perf_checkbox.setChecked(self.track_performance) - self.perf_checkbox.toggled.connect(self._on_perf_changed) - layout.addWidget(self.perf_checkbox) - - # Usage tracking - self.usage_checkbox = QCheckBox("Track Feature Usage (Opt-in)") - self.usage_checkbox.setChecked(self.track_usage) - self.usage_checkbox.toggled.connect(self._on_usage_changed) - layout.addWidget(self.usage_checkbox) - - # Data management - layout.addWidget(QLabel("Data Management:")) - - export_btn = QPushButton("Export Analytics Data") - export_btn.clicked.connect(self._export_data) - layout.addWidget(export_btn) - - clear_all_btn = QPushButton("Clear All Analytics Data") - clear_all_btn.setStyleSheet("color: #f44336;") - clear_all_btn.clicked.connect(self._clear_all_data) - layout.addWidget(clear_all_btn) - - layout.addStretch() - - return tab - - def _update_overview(self): - """Update overview tab.""" - try: - # Get current stats - cpu = psutil.cpu_percent(interval=0.5) - memory = psutil.virtual_memory() - - # Health status - if cpu < 50 and memory.percent < 80: - status = "✓ Healthy" - color = "#4caf50" - elif cpu < 80 and memory.percent < 90: - status = "⚠ Warning" - color = "#ff9800" - else: - status = "✗ Critical" - color = "#f44336" - - self.health_status.setText(status) - self.health_status.setStyleSheet(f"font-size: 16px; font-weight: bold; color: {color};") - - self.cpu_label.setText(f"CPU: {cpu:.1f}%") - self.memory_label.setText(f"Memory: {memory.percent:.1f}%") - - # Uptime - uptime = time.time() - self.start_time - hours = int(uptime // 3600) - minutes = int((uptime % 3600) // 60) - self.uptime_label.setText(f"Uptime: {hours}h {minutes}m") - - # Stats - self.events_label.setText(f"Events recorded: {len(self.session_events)}") - self.errors_label.setText(f"Total errors: {len(self.error_data)}") - - except Exception as e: - self.log_error(f"Overview update failed: {e}") - - def _update_performance_tab(self): - """Update performance tab.""" - try: - # Current stats - cpu = psutil.cpu_percent(interval=0.5) - memory = psutil.virtual_memory() - process = psutil.Process() - process_mem = process.memory_info().rss / 1024 / 1024 - - self.perf_cpu.setText(f"CPU Usage: {cpu:.1f}%") - self.perf_memory.setText(f"Memory Usage: {memory.percent:.1f}%") - self.perf_process.setText(f"Process Memory: {process_mem:.1f} MB") - - # Historical data (last 20 records) - recent_data = self.performance_data[-20:] - self.perf_table.setRowCount(len(recent_data)) - - for i, record in enumerate(reversed(recent_data)): - time_str = record['timestamp'][11:19] # HH:MM:SS - self.perf_table.setItem(i, 0, QTableWidgetItem(time_str)) - self.perf_table.setItem(i, 1, QTableWidgetItem(f"{record['cpu_percent']:.1f}%")) - self.perf_table.setItem(i, 2, QTableWidgetItem(f"{record['memory_percent']:.1f}%")) - self.perf_table.setItem(i, 3, QTableWidgetItem(f"{record['memory_used_mb']:.1f}")) - - except Exception as e: - self.log_error(f"Performance tab update failed: {e}") - - def _update_usage_tab(self): - """Update usage tab.""" - self.usage_table.setRowCount(len(self.usage_data)) - - for i, (feature, data) in enumerate(sorted(self.usage_data.items())): - self.usage_table.setItem(i, 0, QTableWidgetItem(feature)) - self.usage_table.setItem(i, 1, QTableWidgetItem(str(data['count']))) - - last_used = data.get('last_used', 'Never') - if last_used and last_used != 'Never': - last_used = last_used[:16].replace('T', ' ') # Format datetime - self.usage_table.setItem(i, 2, QTableWidgetItem(last_used)) - - def _update_errors_tab(self): - """Update errors tab.""" - self.error_table.setRowCount(len(self.error_data)) - - for i, error in enumerate(reversed(self.error_data[-50:])): # Last 50 errors - time_str = error['timestamp'][11:19] - self.error_table.setItem(i, 0, QTableWidgetItem(time_str)) - self.error_table.setItem(i, 1, QTableWidgetItem(error['message'][:50])) - self.error_table.setItem(i, 2, QTableWidgetItem(str(error.get('context', ''))[:50])) - - def _refresh_all(self): - """Refresh all tabs.""" - self._update_overview() - self._update_performance_tab() - self._update_usage_tab() - self._update_errors_tab() - - def _on_enable_changed(self, checked): - """Handle analytics enable toggle.""" - self.enabled = checked - self.save_data("enabled", checked) - - if checked and self.track_performance: - self._setup_performance_monitoring() - elif not checked and hasattr(self, 'performance_timer'): - self.performance_timer.stop() - - def _on_perf_changed(self, checked): - """Handle performance tracking toggle.""" - self.track_performance = checked - self.save_data("track_performance", checked) - - if checked and self.enabled: - self._setup_performance_monitoring() - elif hasattr(self, 'performance_timer'): - self.performance_timer.stop() - - def _on_usage_changed(self, checked): - """Handle usage tracking toggle.""" - self.track_usage = checked - self.save_data("track_usage", checked) - - def _export_data(self): - """Export analytics data.""" - data = { - 'exported_at': datetime.now().isoformat(), - 'usage': self.usage_data, - 'performance_samples': len(self.performance_data), - 'errors': len(self.error_data) - } - - # Save to file - export_path = os.path.expanduser('~/.eu-utility/analytics_export.json') - os.makedirs(os.path.dirname(export_path), exist_ok=True) - - with open(export_path, 'w') as f: - json.dump(data, f, indent=2) - - self.notify_info("Export Complete", f"Data exported to:\n{export_path}") - - def _clear_all_data(self): - """Clear all analytics data.""" - self.usage_data = {} - self.performance_data = [] - self.error_data = [] - self.session_events = [] - - self.save_data("usage", {}) - self.save_data("performance", []) - self.save_data("errors", []) - - self._refresh_all() - self.notify_info("Data Cleared", "All analytics data has been cleared.") - - def _clear_errors(self): - """Clear error log.""" - self.error_data = [] - self.save_data("errors", []) - self._update_errors_tab() - - def on_show(self): - """Update when tab shown.""" - self._refresh_all() - - def shutdown(self): - """Cleanup on shutdown.""" - if hasattr(self, 'performance_timer'): - self.performance_timer.stop() - - # Record final stats - if self.enabled: - self.save_data("final_session", { - 'session_duration': time.time() - self.start_time, - 'events_recorded': len(self.session_events), - 'timestamp': datetime.now().isoformat() - }) diff --git a/plugins/auto_updater/__init__.py b/plugins/auto_updater/__init__.py deleted file mode 100644 index 9ea3594..0000000 --- a/plugins/auto_updater/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .plugin import AutoUpdaterPlugin - -__all__ = ['AutoUpdaterPlugin'] diff --git a/plugins/auto_updater/__pycache__/__init__.cpython-312.pyc b/plugins/auto_updater/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 96d5bc942befd4741bae5d8c3d64217d3b341f23..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 208 zcmX@j%ge<81c6WcGu?soV-N=hn4pZ$VnD`ph7^Vr#vF!R#wbQch7_iB#weyrW=)ot zj6g|E##@4pr6u{H1u2OosYL-frRkY@Ma)1MKTYOaYy}AZE%x~M#GIV?_>~NwLB{`5 zfJheWCj#}ymqHBCkB`sH%PfhH*DI*}#bJ}1pHiBWYFESw)Bv)sSO7?TU}j`wyvv~a QfLrbYmwY385j#*209`mVX8-^I diff --git a/plugins/auto_updater/__pycache__/plugin.cpython-312.pyc b/plugins/auto_updater/__pycache__/plugin.cpython-312.pyc deleted file mode 100644 index 8aeada485ad6ad11ba58d78143a2c247e435349a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25950 zcmdsgdvFtHo?z?gw$!pDTejsFmfse}GByT-A%J3Ht4yX@UjS9j%*>?Oz4 zJ$}D_OKu>^=C5txufOl>@BR3Fzvst)a5!uPJcs__QsmZNg7|w3$d5(xe3}H$6@n%P z2--j!qlPi#fN{(;U>Y+In8z#wmNDyqbu4cnZ;Tuu$MOgA$7}<(G5dghj2fT}n8y@# zj1>$Nj5!CKW3B<$n0vr2#G9jqV?_f+g5MJL40s@|C0aaCEWAqwN`!amK&kNd4tRxk z*+7}_E*~g|w>4TZRxwavAdJL*g3f!HpviE-TPCsX1C>H-KE&F>MM`Ydw8d{vyldNg zvh`#<5{<;CeLa)$SnJ6NIus8xHru{%C_c%A*=}2_Z}&)e@U(9z#`q?M&#Z5ViH-U8 zN8-Jcr{MdZ*wlD57NUKjaoQIdXXBx0R8A^n4~-9pqp@Kh9bqS;p=pT2%Em(R$e@pj zMWd%eP=0LOHx!CQp;GvMVsIo(Pe#MERE`hAgQr>B1MHRG&gUI_Eb?hh;8-{+dyFv_~f2==zHoljRVAjJB z85#~l0T3^AS@iB_Vv`e6Y;5dQ47`3bG@yTk35RIjHZlET{6u7UJQU@v$ND2B#VPe1l3(ZIy224gGYz|xA zGKgsgtU@?X36nxNKb-fLQA%N(BZgpszDy3-Y17NZ07aX@bB}2?)uXkeDX?hCaWQ zx3H7Y44w=IBjb^HFvwH1v;c#XOq8dj6&Rcd#YcEMuC2)lG2n=Y$0mYuYQKdyv*G9v zQwB|A%F(F+htCfN2csdD4F*}P+Xv5uM{6d8iDcVCuyTXqK499!$iT!jWcjC%gBXt%qmq*s(I_? zGG*n;*Cdy`U#l(VtWsAOO6ayl*P zt200Dmh;XiEA=(vr`hYPNKT`^vu5YZji#M@xIQ@e%?!n-y=Z!I_NjNmD;j z_8XZhOkfDJcx?H6Of?2g%;b2&l?C!4rWHQ1e*s|o0d$Q4&u!eFZtPt#n%rf}o{}pE<_|2;nKkRVHS6zs+E*+@(*Z-$S@Xqb4#HLTkTB&7 z9aQ?fF!cs|v;FlmizCUZwluXK8>I9;t0CdHPuz09^=yXfUe_SavxXbQ& zynkHD|60xaV#O#H@CXVJ8~TVn0(12J-GRL%@vFw_eci;b$^3m?reC{zD))7me$#5% zZym%hS6gAoBCG4zj}n}w~SVh z>*x&gLc+5V`2@jSZ9p72df5=dBtl6gp9V~Pwn}%t&V6X zSi_VNfv*96(>C$&0P4#tLtUB1nE+H3D3HJ-$GNsZc(Qd;q|6dF5#0#gR{^9zY)PCz`!v+52*{JgN#$BB7_gV0TD7dGC6)4Kwu)==PzK|AP2J^9Ns({ zi;VNO@YxBV+9ugBBLYEkEOa_dM;MmR19v<$7UoGN{Nf})YL+(v#U)_Nyo(7%*l-YN z!k`SJfi?j8DgxAFa4Ld}H4S=K^uRs`}bh9hgzWZiv-`*MAzu%0WdU!+rxmK6YRbvIvJDyzwq1-P=n?fPU{ zAXT>GAz^gXUAFK=-X%{lR=BYD$9jmVYrhWsfIXmt!pm!$c~t`qbapZ1)`1bU;cW%70KdW%SD?bEg7`tZ zrAOYGqY7dte<283LDdn(n>9QI`skp2`Yi<1QG&0t#<&74MiqovZGryy%mC5-wnc_H zuve|qR)1ciQNV!068__h4fzHq83xw3jNS+oaiE{+f(rONiQr5)8lG<$YoLP-y$y#O zPQX_50vZxy!a*9?AyL4Zg6#Adyic=zeoEb#Ogoe!QlmT3hX_4`L%^HHV^cf{dwmG^ zIkO$(tn3Iv37#B`O-ut62>VUqW|Qe&VF$aUHX-&UP{J&(E@j*KA&{uZ-ga47)$@>m z{XRIb_w0^KT^GA9ZNIpEp*~aI!j-qA?dz7Q0>oJs%o%Sp=WV{#d6(L}R9W{%?~Oy( z553-(so%=gZ@qmaReva1cQ{$zH+OKky5`!{)v0SUS7&bS$*kMPt=o3{bgKGWNvd+0 zD!g>$;*o_tnaWnKvNhSZBRTNBzoCL~fK#oE51Ns+S#SXho8=4u6WA>3L8o)r!o(xU z$$8-Ll+wcCV2CSQNAHLmwdZ7p;8Sua8%50thqTmA+9cNvi}uMiE61o>&WutHQ>;=o zdIzeS5;-t>hp9G2TtP8(w~tcmtSPP>q%+EKNSjYL!>Xm9(jOgK^(i@Y#vb@bfGS`k zH)EYqP&otf%Fi{aeR3N0os;@Mnzg0t zpq<)1KwI%A&j=m|tB^UXgv{k7iX>{-7as}BBs6bem`%_YzCc9ag1~HMLWA)Kxb+!? zIDLLMp9g5&P$U}W^9CXl`@kb0VwwX10X?$=9pPv=3O=Ar#}L{h{w5R&9Cto13PdX) zZM-Q8n3yHPf?qsHj$sxH9uXaWQam59)*ure;wgbADiwq0h=95Q?aRg{nZYn`1`KYD zH?X{cW)alFsUbWf#a@n2|0Um-+>=bnM0`ZxZC@8)Zyck^Q&s4Q>Rc*J5QjU!) zL`8n{?b7=dRoA+%c3s+q#*SqX2yuA4bvx^3m ztR1^P_LsYUzW?3*KR@*Dp${tWoK1fF`QN>i>ikZ+@w>}q71v6ymR_s8TDcg#)qDHc zy9ZNcJLjz*mo;3rLfP=ycGb4HDdk=JkjQs5T{a^{j0onT`9q5(H$5p&;Ics*=gHJ` za5Wvb_M|*pE*tI_6<_I^@4E8L{4mk0lx#3}|HIPXU(h$06vy?jv3VOs6wJu6c zbn{Di3_vR*{v?j2QG!#Z#G2!>ye%G6Fj__O7I^Ug;OX<*HQ+}8I(Rf7NMPb&n)xnv zPk>5-&;$VzgMuFh;f#!cN_a5KJEFjzW8)HoE*tYisYoutCo|>#6uz-R4a=Gt~!W-q+%VjJwRr5@$eEa3Rdqw5T^^G_7U*C^7=q*pCy@zY> z`Jn4g$1k>~>W^GLEJ6x-^U;~cF0QdFUAkG`f^v*^v%C=x-|Eb4*u!nubJw$X#X)!~ zfcNEE{{^t9^4<3+m$>7;zq@&NEAh)#>hBwc$v6o?zrbHTd4cN`3t@G9Hk4U9|SM# zyI0~{^enQel9mfcJ~LY^wGRubEM*V3+bwku%dM6_F+6mwu{8g$orVI7TfoYNVh5o_ z<&hRM@##23Ws5dx%#2(E`N0=IbD<1gmjC}b9?8b%buCSo=`y97zMcqB7| zbW_8B77_@KAOZ$ax4zh0E9++Q&>!=mWKfW4!(luf5XyK&VO>6{5eZ9sV0~acZ#x8R z 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%0D0u)6)yGox_xg!+}X zsvg}|E4(csSBsij_R%FX=IqrDmUX4G=5yxhQt_=?*7?1OGLc9Tt;)4x_dufPgv4qP zl8F2V<`UHN0M#2nM!6MUJQ)uS3(6L};Y`An%ep`v3VJ$G+80wvWMAkEkT!^n2LyGz zM44QXNFS@;YZA2z3qquLb@(j(vNf2mM=m!(J}a?W679YSs}>lLtm;u+5D`aNM*zuq ziJ%K43f1OdykMF?2VLEl@W@}pK|{m&Fr83%FhiZ48#Zj(Dr@G6It7AxeVFd{_4!Mf zALEGS0R}8kH1ilL*ngnE_vEf%&(QzA0!uvjM`2g|=it@-oCF>Q`gpt0miO;Y4Ep2o6Z6RlF9_ zPr$1IpAoPGJdw4??uxl4fAhzMC7HqouCO6fxQ;7ace6j!`V80lOsa7E-0tPl>MJwz zGmE=#6{br&=X#eYk7#8F3VI*cuD#KEz4i6BxjkvBda1TCQ;XQetx>MFCsW(Y)%M;g zO4S|~z6v&aXq}g#S~;pUxqjydo=i_a*VCWec`_MzHcdSTa__RLjJJjJwq(3*oVV?k zHM#j%%6ok70GQZ`_JSFziKCis=7S9(BuDeUdd^wD2w2Ewu3_`-`b>8p*WH(L9!VZQ z@uBm?5~Og|aITt+%g?z0SKOO+?bAfIajv%H`u?=*Bt%|u&O0-%wVVsEx3p{h$C%g0 zxqM5W#-)-q3-L^K2Up#ZT(cp$flhkD|7bC}3s-D}yEwaw?P=GJRaNw)UAwZY*m&2~ z{>6Q#7wro-3;jbmzc{wZ=51+eyC$-Qqgs;do)II_*f3euajX4SU9w_RhU(&|F2T5P z^CQ*=%jNdoXL?EEH=BCg@VZ0hA80V$arf8`_)Nd^Ss*ZoNSFK|=`SE*55a?aGB1OT zm=UinP+0M^W+AMgPauewkRLDp=cmW z2?;Sw#O6Ll1w#SDRR#_sq({}W*h5@03>t@tLE{U6Q|fDL%o|^Enjyt3@X`mNTj-kt zo4LM6h5%U3S>QgBNSv8~vcbsreR%PPvsp&I`Czg$Cg}MjHtK*CnIR#;w!pS%Y%mmM zw+EDL{Rn69Ous-Z)i+LKiK|Av=f4fkpx*vrAN z6U=kyjDX`e@fHS|8N6vMbe4Gmqw+<-Xgt%-8_#qw9K`X)aR`ie@P-I)JPY2l9V{}q zviTLvZy_pC`}DpX$A}}(#y;bUYIl9bCigl`|grg*`I7!U|#Eb zWA{zt>-$rs>r(F4heTd}`CQLZdBsl#7i(V|{)?8&g_nDNSbVd6ZkKN4$4>Xk4o>AmaRCUkd;EhC2ALDtrX zfQxmE8dEUDxN}lsP>%wl&BKZ<3TiE9wL_st)WV+94pfJez=p6b4RjuEZkj|F^p+Kn zGOAE1U%Kpk+6J}jY6j8vpPJ&h4uwZg^GDCh=qWjLD6j;~!CPjr7Sxe?N(qHhgEX(x zU9e9K)26R}>%upl19jzco;aeY!Xtz5+&AcNmB?xID8|tm`K$U?=^{8@>g0IP3P&3v zX+;3ts*D!Qa=n~NeUCP(e%UwbmwoCRa<5fGvhT@yCy%L7PisWK#`SVN>U*?9^~*ka zGzBU|uaBK_O7;DiK0Zj)(Bfm{-4p+vQGX@|gb*V1RcAutz@ z2l{v`T$&UHGjWS>g-5~*c}3x*PqJ)>TyU?HNNe$)Q%r0M?9LSnZ@j%JQLsBU4&{x> zwKnyS0NFGZQEn8|To!^fk&J;P_62OVejuho6L6#t3L)yC-%LrYhXnVt``ys_e|zwWsCQgYi1Y3h4RR3T6(Kv}u#Id^@=-ORb0Z+dTaq}-e4toNM7 zLPor_WYKe@;(EpFRk!kzTTY~@e#rQ7Y1Kk#emYsx#g%TJqktdluFSaVIafX2H3D|Y zKRCPucdZm`yhUd=9pE+{NN)Vr3Sm5G@Pg~`LVAZcLp5?#V}@GGQET7+PNsD~*SbH` z`YjIrQQ!L1k>|F}SpXTK+^-(YcmkX!knwbIo{kUga5+mMa)8SPJFfHd=igv%OkJP4 zasK-Gzhr(s_3qTq&%b;AS7%dQM^jD5Ql-b|sO1vxmErl}Oi43W(wy|~OqKM^+3%Os zE^fP3dHbbQ$w5?!2J#bcFUXMD%;Qa5!=_Bb4z6KGn%t?R5gF~WIHQZJ>&n#a;Oer{ z)Ny28h7_3KU~j#LTel}o?$uDfB6~VbuGJ8}LW%8ZvH@!nBo&H40&H#tQWvXRM!6i#fq_s zDBL3$8xQPDI3+$6(qs@BPSgM>LrJ&q$U!a4Q_Vy`Z%4|rPvRL#L6L<7NbnHlhlV01ZJPEJ50!1{5=Yi`f*&qZo-v*Fy1m zW8&?L+2&ynAXB122h4y(I0v*{LSXY}kUoo@qe$!i)?O$;kUEa4L&k!A-u@%UlB;H^ zsQj`&S+PCo+VNSQ*--!t3Wrt4oh za^G4?3+Nd|-lIofz}=C?VTH8q>W&8wrqs0n7{Uaj-$y<6dujuEitv9f?IOvJNS`~QRzM3o+KOG-v-0loe7Gw3&qyaCiH@LX9l zzXr7mimsH-mtLuyul&iWYr|KEuf?v$-ahrt@SDT$#NLek)v5P~-y42E_FnAwBdP7r zr2^kht$9BAoltTxnsSdpb`pf}g3!Kku`yZZhofMbv|l1Gl9%0oZeP8g6;{}Pf|dHT z^{gvFP~beQcIb-u=vv?6S(5Xp3^$;!6?m*CdxR50XI;who;Jb_915JCHmjFaY1Wov z@t`(BtzTZxD6Ra4+oJYMq2x3zcyhmhZvYqC2)qI~0ZE|damIv4Z?EXx^YN4%I?~Nm z)cjYj6K@W=Y+wvewMeg!d!xP)I*2`%*``2SE1z{&mLA3aP3Ndq3RFCCpX`-aPwpG& zdN~Q9+j6}hJ>uj)KGog0$+2)aPV9X{Q0VN!@9% zEB$f{)wjGW5nfN5L8__n9YCtN8E!>U>qk*h&hwO=ZjZOg8Pt1C)E0qs(mZQDXPq{S zZ;f=4N)&^IuN~jh{6J-K6;g)NGI0-BCD^cn-oRDQTq8Lax zm82qm==W-lJb@l`~x~VCnbXx2I{2&44!?0 z@J~b(TOsJr6UkEC=gcTN{~a9ODh`ZDoNj;^7B_;#;})sYe+v=19BxLs`#@4ed=n4R z_rQTeRF@{x+&FML?Ody1kc)e69K3$;^~1NeC42hQ)JY%#?-zQ1Qh2TWYB{K! z+i}F8z1rmTt*+Z0?{0;Q>n>a6o4C$jJ^wcI&eWSz@0@@0{9iHePrWzw{`vRL|Nd-h z$G1~$&!?&fE+c6T*PY-6Cfnz?FRn`$wc*_ehvyH2-_wS-vVc%<(R!0gyEfi;Rb*UE zoU3WcasZN6aYRd4_Lt~7lQ#nGWt!4N{JW~u*k^?Nw6*|gTH$I&=aEwXNJ3fC+47-$?{6YANa z4&SAMiv`J2|IMzug2zH2;Ci- zqnEsdJw&tudRSQ}>t13HFP~eSx=Xe_p@ZLsY*z>{i!%JofT-AvaKQ^O!69CJ9y7~$ zm5Q81^OjKvn0b@moT!q9KxBW)n!=JUNdOcTP+a!_8D$TUX8@W};Jb;wm%zdC`9214 zd}M0C5)ss@^X0rXZaBX0o#9Rf&?j(>c3~#5yBH;qu2e=q&5LleQ z!aRSC&c8rMI1OG#-*3@bMCSoI0)R)rEG&2i7X*VHe3K9wA_8v2co=kk2!AX#>}BE; zquD~PPy|(eVb4-Q@rC_MjLO 0: - self.status_label.setText("Status: Update available!") - self.status_label.setStyleSheet("color: #4caf50; font-weight: bold;") - self.update_btn.setEnabled(True) - - self.notify_info( - "Update Available", - f"Version {self.latest_version} is available. Check the Auto Updater to install." - ) - else: - self.status_label.setText("Status: Up to date") - self.status_label.setStyleSheet("color: #4caf50;") - self.update_btn.setEnabled(False) - - except Exception as e: - self.status_label.setText(f"Status: Check failed") - self.status_label.setStyleSheet("color: #f44336;") - self.log_error(f"Update check failed: {e}") - - def _version_compare(self, v1, v2): - """Compare two version strings. Returns 1 if v1 > v2, -1 if v1 < v2, 0 if equal.""" - def normalize(v): - return [int(x) for x in v.split('.')] - - n1 = normalize(v1) - n2 = normalize(v2) - - for i in range(max(len(n1), len(n2))): - x1 = n1[i] if i < len(n1) else 0 - x2 = n2[i] if i < len(n2) else 0 - - if x1 > x2: - return 1 - elif x1 < x2: - return -1 - - return 0 - - def _start_update(self): - """Start the update process.""" - if not self.latest_release: - QMessageBox.warning(self.get_ui(), "No Update", "Please check for updates first.") - return - - # Get download URL - assets = self.latest_release.get('assets', []) - if not assets: - QMessageBox.critical(self.get_ui(), "Error", "No update package found.") - return - - download_url = assets[0]['browser_download_url'] - - # Confirm update - reply = QMessageBox.question( - self.get_ui(), - "Confirm Update", - f"This will update EU-Utility to version {self.latest_version}.\n\n" - "The application will need to restart after installation.\n\n" - "Continue?", - QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No - ) - - if reply != QMessageBox.StandardButton.Yes: - return - - # Start update worker - install_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - backup_path = os.path.expanduser('~/.eu-utility/backups') - - self.worker = UpdateWorker(download_url, install_path, backup_path) - self.worker.progress.connect(self.progress_bar.setValue) - self.worker.status.connect(self.progress_status.setText) - self.worker.finished_signal.connect(self._on_update_finished) - - self.progress_bar.setVisible(True) - self.progress_bar.setValue(0) - self.update_btn.setEnabled(False) - - self.worker.start() - - def _on_update_finished(self, success, message): - """Handle update completion.""" - self.progress_bar.setVisible(False) - - if success: - QMessageBox.information( - self.get_ui(), - "Update Complete", - f"{message}\n\nClick OK to restart EU-Utility." - ) - self._restart_application() - else: - QMessageBox.critical( - self.get_ui(), - "Update Failed", - f"Update failed: {message}\n\nRollback was attempted." - ) - self.update_btn.setEnabled(True) - - def _restart_application(self): - """Restart the application.""" - python = sys.executable - script = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'core', 'main.py') - subprocess.Popen([python, script]) - sys.exit(0) - - def _rollback_dialog(self): - """Show rollback dialog.""" - backup_path = os.path.expanduser('~/.eu-utility/backups') - - if not os.path.exists(backup_path): - QMessageBox.information(self.get_ui(), "No Backups", "No backups found.") - return - - backups = sorted(os.listdir(backup_path)) - if not backups: - QMessageBox.information(self.get_ui(), "No Backups", "No backups found.") - return - - # Show simple rollback for now - reply = QMessageBox.question( - self.get_ui(), - "Confirm Rollback", - f"This will restore the most recent backup:\n{backups[-1]}\n\n" - "The application will restart. Continue?", - QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No - ) - - if reply == QMessageBox.StandardButton.Yes: - try: - backup = os.path.join(backup_path, backups[-1]) - install_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - - # Restore - if os.path.exists(install_path): - shutil.rmtree(install_path) - shutil.copytree(backup, install_path) - - QMessageBox.information( - self.get_ui(), - "Rollback Complete", - "Rollback successful. Click OK to restart." - ) - self._restart_application() - - except Exception as e: - QMessageBox.critical(self.get_ui(), "Rollback Failed", str(e)) - - def _on_startup_changed(self, checked): - """Handle startup check toggle.""" - self.check_on_startup = checked - self.save_data("check_on_startup", checked) - - def _on_auto_changed(self, checked): - """Handle auto-install toggle.""" - self.auto_install = checked - self.save_data("auto_install", checked) - - def _on_interval_changed(self, index): - """Handle check interval change.""" - intervals = [1, 6, 12, 24, 168] # hours - self.check_interval_hours = intervals[index] - self.save_data("check_interval", self.check_interval_hours) diff --git a/plugins/dashboard/__init__.py b/plugins/dashboard/__init__.py deleted file mode 100644 index 448dc14..0000000 --- a/plugins/dashboard/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -""" -Dashboard Plugin -""" - -from .plugin import DashboardPlugin - -__all__ = ["DashboardPlugin"] diff --git a/plugins/dashboard/__pycache__/__init__.cpython-312.pyc b/plugins/dashboard/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index eb94fc5ee4e8ee6b23c1fae3c6b8add5f00d1c8c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 243 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 pA=;kl^cw9mB0qra=D#6Y{s+= 2: # 2 columns - col = 0 - row += 1 - - def _create_widget_card(self, widget_id, name, icon_name): - """Create a stat widget card.""" - card = QFrame() - card.setStyleSheet(f""" - QFrame {{ - background-color: {EU_COLORS['bg_secondary']}; - border: 1px solid {EU_COLORS['border_default']}; - border-radius: 8px; - }} - """) - - layout = QVBoxLayout(card) - layout.setContentsMargins(15, 15, 15, 15) - layout.setSpacing(8) - - # Title - title = QLabel(name) - title.setStyleSheet(f"color: {EU_COLORS['text_muted']}; font-size: 11px;") - layout.addWidget(title) - - # Value - value = self.widget_data.get(widget_id, 0) - - if widget_id == 'ped_balance': - value_text = f"{value:.2f} PED" - elif widget_id == 'play_time': - value_text = "2h 34m" # Placeholder - elif widget_id == 'current_dpp': - value_text = "3.45" - else: - value_text = str(value) - - value_label = QLabel(value_text) - value_label.setStyleSheet(f""" - color: {EU_COLORS['accent_orange']}; - font-size: 24px; - font-weight: bold; - """) - layout.addWidget(value_label) - - layout.addStretch() - return card - - def _show_customize_dialog(self): - """Show widget customization dialog.""" - dialog = QDialog() - dialog.setWindowTitle("Customize Dashboard") - dialog.setStyleSheet(f""" - QDialog {{ - background-color: {EU_COLORS['bg_secondary']}; - color: white; - }} - QLabel {{ - color: white; - }} - """) - - layout = QVBoxLayout(dialog) - - # Instructions - info = QLabel("Check widgets to display on dashboard:") - info.setStyleSheet(f"color: {EU_COLORS['text_secondary']};") - layout.addWidget(info) - - # Widget list - list_widget = QListWidget() - list_widget.setStyleSheet(f""" - QListWidget {{ - background-color: {EU_COLORS['bg_secondary']}; - color: white; - border: 1px solid {EU_COLORS['border_default']}; - }} - QListWidget::item {{ - padding: 10px; - }} - """) - - for widget_id, widget_info in self.AVAILABLE_WIDGETS.items(): - item = QListWidgetItem(widget_info['name']) - item.setFlags(item.flags() | Qt.ItemFlag.ItemIsUserCheckable) - item.setCheckState( - Qt.CheckState.Checked if widget_id in self.enabled_widgets - else Qt.CheckState.Unchecked - ) - item.setData(Qt.ItemDataRole.UserRole, widget_id) - list_widget.addItem(item) - - layout.addWidget(list_widget) - - # Buttons - buttons = QDialogButtonBox( - QDialogButtonBox.StandardButton.Save | QDialogButtonBox.StandardButton.Cancel - ) - buttons.accepted.connect(dialog.accept) - buttons.rejected.connect(dialog.reject) - layout.addWidget(buttons) - - if dialog.exec() == QDialog.DialogCode.Accepted: - # Save selection - self.enabled_widgets = [] - for i in range(list_widget.count()): - item = list_widget.item(i) - if item.checkState() == Qt.CheckState.Checked: - self.enabled_widgets.append(item.data(Qt.ItemDataRole.UserRole)) - - self._save_config() - self._update_widgets() diff --git a/plugins/import_export/__init__.py b/plugins/import_export/__init__.py deleted file mode 100644 index e1ed8ef..0000000 --- a/plugins/import_export/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .plugin import ImportExportPlugin - -__all__ = ['ImportExportPlugin'] diff --git a/plugins/import_export/__pycache__/__init__.cpython-312.pyc b/plugins/import_export/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index eda85c67ad24d188adb4135300b405887eaf7c7a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 210 zcmX@j%ge<81lo`KGd+RyV-N=hn4pZ$VnD`ph7^Vr#vF!R#wbQch7_iB#weyrW=)ot zj6g|E##=(3xdr(}C9V}9G9afkJu|O}87Sqa$$X2g0Kvb-9v`2WlM^4mlHoJR{9lR? z$zuIXumSO@U=#G?<1_OzOXB183Mzkb*yQG?l;)(`6>$P}fGjK)01_XV85tSxGN?V^ PmcPKI(8ylI4ip3c+%Yv{ diff --git a/plugins/import_export/__pycache__/plugin.cpython-312.pyc b/plugins/import_export/__pycache__/plugin.cpython-312.pyc deleted file mode 100644 index 5636354d2e12de4f329de4a471d9793cdb6d6c5c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16792 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 h-tldkK1R#wg}W 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 diff --git a/plugins/plugin_store_ui/plugin.py b/plugins/plugin_store_ui/plugin.py deleted file mode 100644 index 6a92ae6..0000000 --- a/plugins/plugin_store_ui/plugin.py +++ /dev/null @@ -1,273 +0,0 @@ -""" -EU-Utility - Plugin Store UI Plugin - -Browse and install community plugins. -""" - -from PyQt6.QtWidgets import ( - QWidget, QVBoxLayout, QHBoxLayout, QLabel, - QPushButton, QLineEdit, QListWidget, QListWidgetItem, - QProgressBar, QFrame, QTextEdit -) -from PyQt6.QtCore import Qt, QThread, pyqtSignal - -from plugins.base_plugin import BasePlugin - - -class PluginStoreUIPlugin(BasePlugin): - """Browse and install community plugins.""" - - name = "Plugin Store" - version = "1.0.0" - author = "ImpulsiveFPS" - description = "Community plugin marketplace" - hotkey = "ctrl+shift+slash" - - def initialize(self): - """Setup plugin store.""" - self.available_plugins = [] - self.installed_plugins = [] - self.is_loading = False - - def get_ui(self): - """Create plugin store UI.""" - widget = QWidget() - widget.setStyleSheet("background: transparent;") - layout = QVBoxLayout(widget) - layout.setSpacing(15) - layout.setContentsMargins(0, 0, 0, 0) - - # Title - title = QLabel("🛒 Plugin Store") - title.setStyleSheet("color: white; font-size: 16px; font-weight: bold;") - layout.addWidget(title) - - # Search - search_layout = QHBoxLayout() - self.search_input = QLineEdit() - self.search_input.setPlaceholderText("Search plugins...") - self.search_input.setStyleSheet(""" - QLineEdit { - background-color: rgba(30, 35, 45, 200); - color: white; - border: 1px solid rgba(100, 110, 130, 80); - border-radius: 4px; - padding: 8px; - } - """) - search_layout.addWidget(self.search_input) - - search_btn = QPushButton("🔍") - search_btn.setFixedSize(32, 32) - search_btn.setStyleSheet(""" - QPushButton { - background-color: #ff8c42; - border: none; - border-radius: 4px; - } - """) - search_btn.clicked.connect(self._search_plugins) - search_layout.addWidget(search_btn) - - layout.addLayout(search_layout) - - # Categories - cats_layout = QHBoxLayout() - for cat in ["All", "Hunting", "Mining", "Crafting", "Tools", "Social"]: - btn = QPushButton(cat) - btn.setStyleSheet(""" - QPushButton { - background-color: rgba(255,255,255,15); - color: white; - border: none; - border-radius: 4px; - padding: 5px 10px; - } - QPushButton:hover { - background-color: rgba(255,255,255,30); - } - """) - cats_layout.addWidget(btn) - cats_layout.addStretch() - layout.addLayout(cats_layout) - - # Plugins list - self.plugins_list = QListWidget() - self.plugins_list.setStyleSheet(""" - QListWidget { - background-color: rgba(30, 35, 45, 200); - color: white; - border: 1px solid rgba(100, 110, 130, 80); - border-radius: 6px; - } - QListWidget::item { - padding: 12px; - border-bottom: 1px solid rgba(100, 110, 130, 40); - } - QListWidget::item:hover { - background-color: rgba(255, 255, 255, 10); - } - """) - self.plugins_list.itemClicked.connect(self._show_plugin_details) - layout.addWidget(self.plugins_list) - - # Sample plugins - self._load_sample_plugins() - - # Details panel - self.details_panel = QFrame() - self.details_panel.setStyleSheet(""" - QFrame { - background-color: rgba(30, 35, 45, 200); - border: 1px solid rgba(100, 110, 130, 80); - border-radius: 6px; - } - """) - details_layout = QVBoxLayout(self.details_panel) - - self.detail_name = QLabel("Select a plugin") - self.detail_name.setStyleSheet("color: #ff8c42; font-size: 14px; font-weight: bold;") - details_layout.addWidget(self.detail_name) - - self.detail_desc = QLabel("") - self.detail_desc.setStyleSheet("color: rgba(255,255,255,150);") - self.detail_desc.setWordWrap(True) - details_layout.addWidget(self.detail_desc) - - self.detail_author = QLabel("") - self.detail_author.setStyleSheet("color: rgba(255,255,255,100); font-size: 10px;") - details_layout.addWidget(self.detail_author) - - self.install_btn = QPushButton("Install") - self.install_btn.setStyleSheet(""" - QPushButton { - background-color: #4caf50; - color: white; - padding: 10px; - border: none; - border-radius: 4px; - font-weight: bold; - } - """) - self.install_btn.clicked.connect(self._install_plugin) - self.install_btn.setEnabled(False) - details_layout.addWidget(self.install_btn) - - layout.addWidget(self.details_panel) - - # Refresh button - refresh_btn = QPushButton("🔄 Refresh") - refresh_btn.setStyleSheet(""" - QPushButton { - background-color: rgba(255,255,255,20); - color: white; - padding: 8px; - border: none; - border-radius: 4px; - } - """) - refresh_btn.clicked.connect(self._refresh_store) - layout.addWidget(refresh_btn) - - layout.addStretch() - return widget - - def _load_sample_plugins(self): - """Load sample plugin list.""" - sample = [ - { - 'name': 'Crafting Calculator', - 'description': 'Calculate crafting success rates and costs', - 'author': 'EU Community', - 'version': '1.0.0', - 'downloads': 542, - 'rating': 4.5, - }, - { - 'name': 'Global Tracker', - 'description': 'Track globals and HOFs with notifications', - 'author': 'ImpulsiveFPS', - 'version': '1.2.0', - 'downloads': 1203, - 'rating': 4.8, - }, - { - 'name': 'Bank Manager', - 'description': 'Manage storage and bank items across planets', - 'author': 'StorageMaster', - 'version': '0.9.0', - 'downloads': 328, - 'rating': 4.2, - }, - { - 'name': 'Society Tools', - 'description': 'Society management and member tracking', - 'author': 'SocietyDev', - 'version': '1.0.0', - 'downloads': 215, - 'rating': 4.0, - }, - { - 'name': 'Team Helper', - 'description': 'Team coordination and loot sharing', - 'author': 'TeamPlayer', - 'version': '1.1.0', - 'downloads': 876, - 'rating': 4.6, - }, - ] - - self.available_plugins = sample - self._update_list() - - def _update_list(self): - """Update plugins list.""" - self.plugins_list.clear() - - for plugin in self.available_plugins: - item = QListWidgetItem( - f"{plugin['name']} v{plugin['version']}\n" - f"⭐ {plugin['rating']} | ⬇ {plugin['downloads']}" - ) - item.setData(Qt.ItemDataRole.UserRole, plugin) - self.plugins_list.addItem(item) - - def _show_plugin_details(self, item): - """Show plugin details.""" - plugin = item.data(Qt.ItemDataRole.UserRole) - if plugin: - self.detail_name.setText(f"{plugin['name']} v{plugin['version']}") - self.detail_desc.setText(plugin['description']) - self.detail_author.setText(f"By {plugin['author']} | ⭐ {plugin['rating']}") - self.install_btn.setEnabled(True) - self.selected_plugin = plugin - - def _install_plugin(self): - """Install selected plugin.""" - if hasattr(self, 'selected_plugin'): - print(f"Installing {self.selected_plugin['name']}...") - self.install_btn.setText("Installing...") - self.install_btn.setEnabled(False) - # TODO: Actual install - - def _search_plugins(self): - """Search plugins.""" - query = self.search_input.text().lower() - - filtered = [ - p for p in self.available_plugins - if query in p['name'].lower() or query in p['description'].lower() - ] - - self.plugins_list.clear() - for plugin in filtered: - item = QListWidgetItem( - f"{plugin['name']} v{plugin['version']}\n" - f"⭐ {plugin['rating']} | ⬇ {plugin['downloads']}" - ) - item.setData(Qt.ItemDataRole.UserRole, plugin) - self.plugins_list.addItem(item) - - def _refresh_store(self): - """Refresh plugin list.""" - self._load_sample_plugins() diff --git a/plugins/settings/__init__.py b/plugins/settings/__init__.py deleted file mode 100644 index b8a1ea7..0000000 --- a/plugins/settings/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -""" -Settings Plugin -""" - -from .plugin import SettingsPlugin - -__all__ = ["SettingsPlugin"] diff --git a/plugins/settings/__pycache__/__init__.cpython-312.pyc b/plugins/settings/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 03633ebf3ab5261ee795092743384388d8f581ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 240 zcmX@j%ge<81pBP}Goyg?V-N=hn4pZ$Qb5LZh7^V#wg}W z7ERVFL9XD`l9J54^kRj8oYM5nJg%3FKs}m_xA}zQ`1o6F z1z-(e36N-FPELIMN`}uMyMM_*B#ZTnp~maS$7kkcmc+;F6;%G>u*uC&Da}c>D+2ie lWMQ#5kodsN$jJDSiHVWrD+dFk@&j(^3tTdd>_r?vIRLUQJ$C>A diff --git a/plugins/settings/__pycache__/plugin.cpython-312.pyc b/plugins/settings/__pycache__/plugin.cpython-312.pyc deleted file mode 100644 index 9595ddf8b9e7d16b00ca8a12e11c37ff41c13c73..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 52393 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

h!bZ&wIWWhzP8 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 C|6=C= diff --git a/plugins/settings/plugin.py b/plugins/settings/plugin.py deleted file mode 100644 index 59659ac..0000000 --- a/plugins/settings/plugin.py +++ /dev/null @@ -1,1174 +0,0 @@ -""" -EU-Utility - Settings UI Plugin - -Settings menu for configuring EU-Utility. -""" - -from PyQt6.QtWidgets import ( - QWidget, QVBoxLayout, QHBoxLayout, QLabel, - QPushButton, QCheckBox, QLineEdit, QComboBox, - QSlider, QTabWidget, QGroupBox, QListWidget, - QListWidgetItem, QFrame, QFileDialog, QScrollArea -) -from PyQt6.QtCore import Qt, QTimer - -from core.settings import get_settings -from plugins.base_plugin import BasePlugin - - -class SettingsPlugin(BasePlugin): - """EU-Utility settings and configuration.""" - - name = "Settings" - version = "1.0.0" - author = "ImpulsiveFPS" - description = "Configure EU-Utility preferences" - hotkey = "ctrl+shift+comma" - - def initialize(self): - """Setup settings.""" - self.settings = get_settings() - - def get_ui(self): - """Create settings UI.""" - widget = QWidget() - widget.setStyleSheet("background: transparent;") - layout = QVBoxLayout(widget) - layout.setSpacing(10) - layout.setContentsMargins(0, 0, 0, 0) - - # Title - title = QLabel("Settings") - title.setStyleSheet("color: white; font-size: 16px; font-weight: bold;") - layout.addWidget(title) - - # Tabs - tabs = QTabWidget() - tabs.setStyleSheet(""" - QTabBar::tab { - background-color: rgba(35, 40, 55, 200); - color: rgba(255,255,255,150); - padding: 10px 20px; - border-top-left-radius: 6px; - border-top-right-radius: 6px; - } - QTabBar::tab:selected { - background-color: #ff8c42; - color: white; - font-weight: bold; - } - """) - - # General tab - general_tab = self._create_general_tab() - tabs.addTab(general_tab, "General") - - # Plugins tab - plugins_tab = self._create_plugins_tab() - tabs.addTab(plugins_tab, "Plugins") - - # Hotkeys tab - hotkeys_tab = self._create_hotkeys_tab() - tabs.addTab(hotkeys_tab, "Hotkeys") - - # Overlay tab - overlay_tab = self._create_overlay_tab() - tabs.addTab(overlay_tab, "Overlays") - - # Data tab - data_tab = self._create_data_tab() - tabs.addTab(data_tab, "Data") - - layout.addWidget(tabs) - - # Save/Reset buttons - btn_layout = QHBoxLayout() - - save_btn = QPushButton("Save Settings") - save_btn.setStyleSheet(""" - QPushButton { - background-color: #4caf50; - color: white; - padding: 10px 20px; - border: none; - border-radius: 4px; - font-weight: bold; - } - """) - save_btn.clicked.connect(self._save_settings) - btn_layout.addWidget(save_btn) - - reset_btn = QPushButton("Reset to Default") - reset_btn.setStyleSheet(""" - QPushButton { - background-color: rgba(255,255,255,20); - color: white; - padding: 10px 20px; - border: none; - border-radius: 4px; - } - """) - reset_btn.clicked.connect(self._reset_settings) - btn_layout.addWidget(reset_btn) - - btn_layout.addStretch() - layout.addLayout(btn_layout) - - return widget - - def _create_general_tab(self): - """Create general settings tab.""" - tab = QWidget() - layout = QVBoxLayout(tab) - layout.setSpacing(15) - - # Appearance - appear_group = QGroupBox("Appearance") - appear_group.setStyleSheet(self._group_style()) - appear_layout = QVBoxLayout(appear_group) - - # Theme - theme_layout = QHBoxLayout() - theme_layout.addWidget(QLabel("Theme:")) - self.theme_combo = QComboBox() - self.theme_combo.addItems(["Dark (EU Style)", "Light", "Auto"]) - self.theme_combo.setCurrentText(self.settings.get('theme', 'Dark (EU Style)')) - theme_layout.addWidget(self.theme_combo) - theme_layout.addStretch() - appear_layout.addLayout(theme_layout) - - # Opacity - opacity_layout = QHBoxLayout() - opacity_layout.addWidget(QLabel("Overlay Opacity:")) - self.opacity_slider = QSlider(Qt.Orientation.Horizontal) - self.opacity_slider.setMinimum(50) - self.opacity_slider.setMaximum(100) - self.opacity_slider.setValue(int(self.settings.get('overlay_opacity', 0.9) * 100)) - opacity_layout.addWidget(self.opacity_slider) - self.opacity_label = QLabel(f"{self.opacity_slider.value()}%") - opacity_layout.addWidget(self.opacity_label) - opacity_layout.addStretch() - appear_layout.addLayout(opacity_layout) - - # Icon size - icon_layout = QHBoxLayout() - icon_layout.addWidget(QLabel("Icon Size:")) - self.icon_combo = QComboBox() - self.icon_combo.addItems(["Small (20px)", "Medium (24px)", "Large (32px)"]) - icon_layout.addWidget(self.icon_combo) - icon_layout.addStretch() - appear_layout.addLayout(icon_layout) - - layout.addWidget(appear_group) - - # Behavior - behavior_group = QGroupBox("Behavior") - behavior_group.setStyleSheet(self._group_style()) - behavior_layout = QVBoxLayout(behavior_group) - - self.auto_start_cb = QCheckBox("Start with Windows") - self.auto_start_cb.setChecked(self.settings.get('auto_start', False)) - behavior_layout.addWidget(self.auto_start_cb) - - self.minimize_cb = QCheckBox("Minimize to tray on close") - self.minimize_cb.setChecked(self.settings.get('minimize_to_tray', True)) - behavior_layout.addWidget(self.minimize_cb) - - self.tooltips_cb = QCheckBox("Show tooltips") - self.tooltips_cb.setChecked(self.settings.get('show_tooltips', True)) - behavior_layout.addWidget(self.tooltips_cb) - - layout.addWidget(behavior_group) - layout.addStretch() - - return tab - - def _create_plugins_tab(self): - """Create plugins management tab with dependency visualization.""" - tab = QWidget() - layout = QVBoxLayout(tab) - layout.setSpacing(15) - - # Info label - info = QLabel("Manage plugins. Hover over dependency icons to see requirements. Changes take effect immediately.") - info.setStyleSheet("color: rgba(255,255,255,150);") - layout.addWidget(info) - - # Dependency legend - legend_layout = QHBoxLayout() - legend_layout.addWidget(QLabel("Legend:")) - - # Required by others indicator - req_label = QLabel("⚠️ Required") - req_label.setStyleSheet("color: #ffd93d; font-size: 11px;") - req_label.setToolTip("This plugin is required by other enabled plugins") - legend_layout.addWidget(req_label) - - # Has dependencies indicator - dep_label = QLabel("🔗 Has deps") - dep_label.setStyleSheet("color: #4ecdc4; font-size: 11px;") - dep_label.setToolTip("This plugin requires other plugins to function") - legend_layout.addWidget(dep_label) - - # Auto-enabled indicator - auto_label = QLabel("🔄 Auto") - auto_label.setStyleSheet("color: #ff8c42; font-size: 11px;") - auto_label.setToolTip("Auto-enabled due to dependency") - legend_layout.addWidget(auto_label) - - legend_layout.addStretch() - layout.addLayout(legend_layout) - - # Scroll area for plugin list - scroll = QScrollArea() - scroll.setWidgetResizable(True) - scroll.setFrameShape(QFrame.Shape.NoFrame) - scroll.setStyleSheet("background: transparent; border: none;") - - scroll_content = QWidget() - plugins_layout = QVBoxLayout(scroll_content) - plugins_layout.setSpacing(8) - plugins_layout.setAlignment(Qt.AlignmentFlag.AlignTop) - - self.plugin_checkboxes = {} - self.plugin_dependency_labels = {} - self.plugin_rows = {} - - # 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() - - # Build dependency maps - self._build_dependency_maps(all_plugins) - - # Sort by name - sorted_plugins = sorted(all_plugins.items(), key=lambda x: x[1].name) - - for plugin_id, plugin_class in sorted_plugins: - plugin_row = self._create_plugin_row(plugin_id, plugin_class, plugin_manager) - plugins_layout.addLayout(plugin_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) - - plugins_layout.addStretch() - scroll.setWidget(scroll_content) - layout.addWidget(scroll) - - # Buttons - btn_layout = QHBoxLayout() - - enable_all_btn = QPushButton("Enable All") - enable_all_btn.setStyleSheet(""" - QPushButton { - background-color: #4caf50; - color: white; - padding: 8px 16px; - border: none; - border-radius: 4px; - } - """) - enable_all_btn.clicked.connect(self._enable_all_plugins) - btn_layout.addWidget(enable_all_btn) - - disable_all_btn = QPushButton("Disable All") - 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) - - # Dependency info button - deps_info_btn = QPushButton("📋 Dependency Report") - deps_info_btn.setStyleSheet(""" - QPushButton { - background-color: #4a9eff; - color: white; - padding: 8px 16px; - border: none; - border-radius: 4px; - } - """) - deps_info_btn.clicked.connect(self._show_dependency_report) - btn_layout.addWidget(deps_info_btn) - - btn_layout.addStretch() - layout.addLayout(btn_layout) - - return tab - - def _build_dependency_maps(self, all_plugins): - """Build maps of plugin dependencies.""" - self.plugin_deps = {} # plugin_id -> list of plugin_ids it depends on - self.plugin_dependents = {} # plugin_id -> list of plugin_ids that depend on it - - for plugin_id, plugin_class in all_plugins.items(): - deps = getattr(plugin_class, 'dependencies', {}) - plugin_deps_list = deps.get('plugins', []) - - self.plugin_deps[plugin_id] = plugin_deps_list - - # Build reverse map - for dep_id in plugin_deps_list: - if dep_id not in self.plugin_dependents: - self.plugin_dependents[dep_id] = [] - self.plugin_dependents[dep_id].append(plugin_id) - - def _create_plugin_row(self, plugin_id, plugin_class, plugin_manager): - """Create a plugin row with dependency indicators.""" - row = QHBoxLayout() - row.setSpacing(10) - - # Checkbox - cb = QCheckBox(plugin_class.name) - is_enabled = plugin_manager.is_plugin_enabled(plugin_id) - is_auto_enabled = plugin_manager.is_auto_enabled(plugin_id) if hasattr(plugin_manager, 'is_auto_enabled') else False - - cb.setChecked(is_enabled) - cb.setStyleSheet(""" - QCheckBox { - color: white; - spacing: 8px; - } - QCheckBox::indicator { - width: 18px; - height: 18px; - } - QCheckBox::indicator:disabled { - background-color: #ff8c42; - } - """) - - # Disable checkbox if auto-enabled - if is_auto_enabled: - cb.setEnabled(False) - cb.setText(f"{plugin_class.name} (auto)") - - # 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) - - # Dependency indicators - indicators_layout = QHBoxLayout() - indicators_layout.setSpacing(4) - - # Check if this plugin has dependencies - deps = self.plugin_deps.get(plugin_id, []) - if deps: - deps_btn = QPushButton("🔗") - deps_btn.setFixedSize(24, 24) - deps_btn.setStyleSheet(""" - QPushButton { - background-color: transparent; - color: #4ecdc4; - border: none; - font-size: 12px; - } - QPushButton:hover { - background-color: rgba(78, 205, 196, 30); - border-radius: 4px; - } - """) - deps_btn.setToolTip(self._format_dependencies_tooltip(plugin_id, deps)) - indicators_layout.addWidget(deps_btn) - - # Check if other plugins depend on this one - dependents = self.plugin_dependents.get(plugin_id, []) - enabled_dependents = [d for d in dependents if plugin_manager.is_plugin_enabled(d)] - if enabled_dependents: - req_btn = QPushButton("⚠️") - req_btn.setFixedSize(24, 24) - req_btn.setStyleSheet(""" - QPushButton { - background-color: transparent; - color: #ffd93d; - border: none; - font-size: 12px; - } - QPushButton:hover { - background-color: rgba(255, 217, 61, 30); - border-radius: 4px; - } - """) - req_btn.setToolTip(self._format_dependents_tooltip(plugin_id, enabled_dependents)) - indicators_layout.addWidget(req_btn) - - # Check if auto-enabled - if is_auto_enabled: - auto_btn = QPushButton("🔄") - auto_btn.setFixedSize(24, 24) - auto_btn.setStyleSheet(""" - QPushButton { - background-color: transparent; - color: #ff8c42; - border: none; - font-size: 12px; - } - QPushButton:hover { - background-color: rgba(255, 140, 66, 30); - border-radius: 4px; - } - """) - # Find what enabled this plugin - enabler = self._find_enabler(plugin_id, plugin_manager) - auto_btn.setToolTip(f"Auto-enabled by: {enabler or 'dependency resolution'}") - indicators_layout.addWidget(auto_btn) - - row.addLayout(indicators_layout) - - # 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() - - # Store row reference for updates - self.plugin_rows[plugin_id] = row - - return row - - def _format_dependencies_tooltip(self, plugin_id, deps): - """Format tooltip for dependencies.""" - lines = ["This plugin requires:"] - for dep_id in deps: - dep_name = dep_id.split('.')[-1].replace('_', ' ').title() - lines.append(f" • {dep_name}") - lines.append("") - lines.append("These will be auto-enabled when you enable this plugin.") - return "\n".join(lines) - - def _format_dependents_tooltip(self, plugin_id, dependents): - """Format tooltip for plugins that depend on this one.""" - lines = ["Required by enabled plugins:"] - for dep_id in dependents: - dep_name = dep_id.split('.')[-1].replace('_', ' ').title() - lines.append(f" • {dep_name}") - lines.append("") - lines.append("Disable these first to disable this plugin.") - return "\n".join(lines) - - def _find_enabler(self, plugin_id, plugin_manager): - """Find which plugin auto-enabled this one.""" - # Check all enabled plugins to see which one depends on this - for other_id, other_class in plugin_manager.get_all_discovered_plugins().items(): - if plugin_manager.is_plugin_enabled(other_id): - deps = getattr(other_class, 'dependencies', {}).get('plugins', []) - if plugin_id in deps: - return other_class.name - return None - - def _show_dependency_report(self): - """Show a dialog with full dependency report.""" - from PyQt6.QtWidgets import QDialog, QTextEdit, QVBoxLayout, QPushButton - - dialog = QDialog() - dialog.setWindowTitle("Plugin Dependency Report") - dialog.setMinimumSize(600, 400) - dialog.setStyleSheet(""" - QDialog { - background-color: #1a1f2e; - } - QTextEdit { - background-color: #232837; - color: white; - border: 1px solid rgba(100, 110, 130, 80); - padding: 10px; - } - QPushButton { - background-color: #4a9eff; - color: white; - padding: 8px 16px; - border: none; - border-radius: 4px; - } - """) - - layout = QVBoxLayout(dialog) - - text_edit = QTextEdit() - text_edit.setReadOnly(True) - text_edit.setHtml(self._generate_dependency_report()) - layout.addWidget(text_edit) - - close_btn = QPushButton("Close") - close_btn.clicked.connect(dialog.close) - layout.addWidget(close_btn) - - dialog.exec() - - def _create_hotkeys_tab(self): - """Create hotkeys configuration tab - dynamically discovers hotkeys from plugins.""" - tab = QWidget() - layout = QVBoxLayout(tab) - layout.setSpacing(15) - - # Info label - info = QLabel("Hotkeys are advertised by plugins. Changes apply on next restart.") - info.setStyleSheet("color: rgba(255,255,255,150);") - layout.addWidget(info) - - # Scroll area for hotkeys - scroll = QScrollArea() - scroll.setWidgetResizable(True) - scroll.setFrameShape(QFrame.Shape.NoFrame) - scroll.setStyleSheet("background: transparent; border: none;") - - scroll_content = QWidget() - hotkeys_layout = QVBoxLayout(scroll_content) - hotkeys_layout.setSpacing(10) - hotkeys_layout.setAlignment(Qt.AlignmentFlag.AlignTop) - - self.hotkey_inputs = {} - - # Collect hotkeys from all plugins - plugin_hotkeys = self._collect_plugin_hotkeys() - - # Group by plugin - for plugin_name, hotkeys in sorted(plugin_hotkeys.items()): - # Plugin group - group = QGroupBox(plugin_name) - group.setStyleSheet(self._group_style()) - group_layout = QVBoxLayout(group) - - for hotkey_info in hotkeys: - row = QHBoxLayout() - - # Description - desc = hotkey_info.get('description', hotkey_info['action']) - desc_label = QLabel(f"{desc}:") - desc_label.setStyleSheet("color: white; min-width: 150px;") - row.addWidget(desc_label) - - # Hotkey input - input_field = QLineEdit() - input_field.setText(hotkey_info['current']) - input_field.setPlaceholderText(hotkey_info['default']) - input_field.setStyleSheet(""" - QLineEdit { - background-color: rgba(30, 35, 45, 200); - color: white; - border: 1px solid rgba(100, 110, 130, 80); - padding: 5px; - min-width: 150px; - } - """) - - # Store reference with config key - config_key = hotkey_info['config_key'] - self.hotkey_inputs[config_key] = { - 'input': input_field, - 'default': hotkey_info['default'], - 'plugin': plugin_name, - 'action': hotkey_info['action'] - } - - row.addWidget(input_field) - - # Reset button - reset_btn = QPushButton("↺") - reset_btn.setFixedSize(28, 28) - reset_btn.setStyleSheet(""" - QPushButton { - background-color: rgba(255,255,255,20); - color: white; - border: none; - border-radius: 4px; - font-size: 12px; - } - QPushButton:hover { - background-color: rgba(255,255,255,40); - } - """) - reset_btn.setToolTip(f"Reset to default: {hotkey_info['default']}") - reset_btn.clicked.connect(lambda checked, inp=input_field, default=hotkey_info['default']: inp.setText(default)) - row.addWidget(reset_btn) - - row.addStretch() - group_layout.addLayout(row) - - hotkeys_layout.addWidget(group) - - # Core hotkeys section (always present) - core_group = QGroupBox("Core System") - core_group.setStyleSheet(self._group_style()) - core_layout = QVBoxLayout(core_group) - - core_hotkeys = [ - ("Toggle Overlay", "hotkey_toggle", "ctrl+shift+u", "Show/hide the EU-Utility overlay"), - ("Universal Search", "hotkey_search", "ctrl+shift+f", "Quick search across all plugins"), - ] - - for label, config_key, default, description in core_hotkeys: - row = QHBoxLayout() - - desc_label = QLabel(f"{label}:") - desc_label.setStyleSheet("color: white; min-width: 150px;") - row.addWidget(desc_label) - - input_field = QLineEdit() - current = self.settings.get(config_key, default) - input_field.setText(current) - input_field.setPlaceholderText(default) - input_field.setStyleSheet(""" - QLineEdit { - background-color: rgba(30, 35, 45, 200); - color: white; - border: 1px solid rgba(100, 110, 130, 80); - padding: 5px; - min-width: 150px; - } - """) - - self.hotkey_inputs[config_key] = { - 'input': input_field, - 'default': default, - 'plugin': 'Core', - 'action': label - } - - row.addWidget(input_field) - - reset_btn = QPushButton("↺") - reset_btn.setFixedSize(28, 28) - reset_btn.setStyleSheet(""" - QPushButton { - background-color: rgba(255,255,255,20); - color: white; - border: none; - border-radius: 4px; - font-size: 12px; - } - QPushButton:hover { - background-color: rgba(255,255,255,40); - } - """) - reset_btn.setToolTip(f"Reset to default: {default}") - reset_btn.clicked.connect(lambda checked, inp=input_field, default=default: inp.setText(default)) - row.addWidget(reset_btn) - - row.addStretch() - core_layout.addLayout(row) - - hotkeys_layout.addWidget(core_group) - hotkeys_layout.addStretch() - - scroll.setWidget(scroll_content) - layout.addWidget(scroll) - - return tab - - def _collect_plugin_hotkeys(self) -> dict: - """Collect hotkeys from all discovered plugins. - - Returns: - Dict mapping plugin name to list of hotkey info dicts - """ - plugin_hotkeys = {} - - if not hasattr(self.overlay, 'plugin_manager'): - return plugin_hotkeys - - plugin_manager = self.overlay.plugin_manager - all_plugins = plugin_manager.get_all_discovered_plugins() - - for plugin_id, plugin_class in all_plugins.items(): - hotkeys = getattr(plugin_class, 'hotkeys', None) - - if not hotkeys: - # Try legacy single hotkey attribute - single_hotkey = getattr(plugin_class, 'hotkey', None) - if single_hotkey: - hotkeys = [{ - 'action': 'toggle', - 'description': f"Toggle {plugin_class.name}", - 'default': single_hotkey, - 'config_key': f"hotkey_{plugin_id.split('.')[-1]}" - }] - - if hotkeys: - plugin_name = plugin_class.name - plugin_hotkeys[plugin_name] = [] - - for i, hk in enumerate(hotkeys): - # Support both dict format and simple string - if isinstance(hk, dict): - hotkey_info = { - 'action': hk.get('action', f'action_{i}'), - 'description': hk.get('description', hk.get('action', f'Action {i}')), - 'default': hk.get('default', ''), - 'config_key': hk.get('config_key', f"hotkey_{plugin_id.split('.')[-1]}_{i}") - } - else: - # Simple string format - legacy - hotkey_info = { - 'action': f'hotkey_{i}', - 'description': f"Hotkey {i+1}", - 'default': str(hk), - 'config_key': f"hotkey_{plugin_id.split('.')[-1]}_{i}" - } - - # Get current value from settings - hotkey_info['current'] = self.settings.get(hotkey_info['config_key'], hotkey_info['default']) - - plugin_hotkeys[plugin_name].append(hotkey_info) - - return plugin_hotkeys - - def _create_overlay_tab(self): - """Create overlay widgets configuration tab.""" - tab = QWidget() - layout = QVBoxLayout(tab) - layout.setSpacing(15) - - overlays_group = QGroupBox("In-Game Overlays") - overlays_group.setStyleSheet(self._group_style()) - overlays_layout = QVBoxLayout(overlays_group) - - overlays = [ - ("Spotify Player", "spotify", True), - ("Mission Tracker", "mission", False), - ("Skill Gains", "skillgain", False), - ("DPP Tracker", "dpp", False), - ] - - for name, key, enabled in overlays: - cb = QCheckBox(name) - cb.setChecked(enabled) - overlays_layout.addWidget(cb) - - # Reset positions - reset_pos_btn = QPushButton("↺ Reset All Positions") - reset_pos_btn.setStyleSheet(""" - QPushButton { - background-color: rgba(255,255,255,20); - color: white; - padding: 8px; - border: none; - border-radius: 4px; - } - """) - overlays_layout.addWidget(reset_pos_btn) - - layout.addWidget(overlays_group) - layout.addStretch() - - return tab - - def _create_data_tab(self): - """Create data management tab.""" - tab = QWidget() - layout = QVBoxLayout(tab) - layout.setSpacing(15) - - data_group = QGroupBox("Data Management") - data_group.setStyleSheet(self._group_style()) - data_layout = QVBoxLayout(data_group) - - # Export - export_btn = QPushButton("📤 Export All Data") - export_btn.setStyleSheet(""" - QPushButton { - background-color: #4a9eff; - color: white; - padding: 10px; - border: none; - border-radius: 4px; - font-weight: bold; - } - """) - export_btn.clicked.connect(self._export_data) - data_layout.addWidget(export_btn) - - # Import - import_btn = QPushButton("📥 Import Data") - import_btn.setStyleSheet(""" - QPushButton { - background-color: rgba(255,255,255,20); - color: white; - padding: 10px; - border: none; - border-radius: 4px; - } - """) - import_btn.clicked.connect(self._import_data) - data_layout.addWidget(import_btn) - - # Clear - clear_btn = QPushButton("Clear All Data") - clear_btn.setStyleSheet(""" - QPushButton { - background-color: #f44336; - color: white; - padding: 10px; - border: none; - border-radius: 4px; - } - """) - clear_btn.clicked.connect(self._clear_data) - data_layout.addWidget(clear_btn) - - # Retention - retention_layout = QHBoxLayout() - retention_layout.addWidget(QLabel("Data retention:")) - self.retention_combo = QComboBox() - self.retention_combo.addItems(["7 days", "30 days", "90 days", "Forever"]) - retention_layout.addWidget(self.retention_combo) - retention_layout.addStretch() - data_layout.addLayout(retention_layout) - - layout.addWidget(data_group) - layout.addStretch() - - return tab - - def _group_style(self): - """Get group box style.""" - return """ - QGroupBox { - color: rgba(255,255,255,200); - border: 1px solid rgba(100, 110, 130, 80); - border-radius: 6px; - margin-top: 10px; - font-weight: bold; - font-size: 12px; - } - QGroupBox::title { - subcontrol-origin: margin; - left: 10px; - padding: 0 5px; - } - """ - - def _save_settings(self): - """Save all settings.""" - # General - self.settings.set('theme', self.theme_combo.currentText()) - self.settings.set('overlay_opacity', self.opacity_slider.value() / 100) - self.settings.set('auto_start', self.auto_start_cb.isChecked()) - self.settings.set('minimize_to_tray', self.minimize_cb.isChecked()) - self.settings.set('show_tooltips', self.tooltips_cb.isChecked()) - - # Hotkeys - new structure with dict values - for config_key, hotkey_data in self.hotkey_inputs.items(): - if isinstance(hotkey_data, dict): - input_field = hotkey_data['input'] - self.settings.set(config_key, input_field.text()) - else: - # Legacy format - direct QLineEdit reference - self.settings.set(config_key, hotkey_data.text()) - - print("Settings saved!") - - def _reset_settings(self): - """Reset to defaults.""" - self.settings.reset() - print("Settings reset to defaults!") - - def _export_data(self): - """Export all data.""" - from PyQt6.QtWidgets import QFileDialog - - filepath, _ = QFileDialog.getSaveFileName( - None, "Export EU-Utility Data", "eu_utility_backup.json", "JSON (*.json)" - ) - - if filepath: - import shutil - data_dir = Path("data") - if data_dir.exists(): - # Create export - import json - export_data = {} - for f in data_dir.glob("*.json"): - with open(f, 'r') as file: - export_data[f.stem] = json.load(file) - - with open(filepath, 'w') as file: - json.dump(export_data, file, indent=2) - - def _import_data(self): - """Import data.""" - pass - - def _clear_data(self): - """Clear all data.""" - pass - - def _toggle_plugin(self, plugin_id: str, enable: bool): - """Enable or disable a plugin with dependency handling.""" - if not hasattr(self.overlay, 'plugin_manager'): - return - - plugin_manager = self.overlay.plugin_manager - - if enable: - # Get dependencies that will be auto-enabled - deps_to_enable = self._get_missing_dependencies(plugin_id, plugin_manager) - - if deps_to_enable: - # Show confirmation dialog - from PyQt6.QtWidgets import QMessageBox - - dep_names = [pid.split('.')[-1].replace('_', ' ').title() for pid in deps_to_enable] - msg = f"Enabling this plugin will also enable:\n\n" - msg += "\n".join(f" • {name}" for name in dep_names) - msg += "\n\nContinue?" - - reply = QMessageBox.question( - None, "Enable Dependencies", msg, - QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No - ) - - if reply != QMessageBox.StandardButton.Yes: - # Uncheck the box - self.plugin_checkboxes[plugin_id].setChecked(False) - return - - success = plugin_manager.enable_plugin(plugin_id) - if success: - print(f"[Settings] Enabled plugin: {plugin_id}") - # Refresh UI to show auto-enabled plugins - self._refresh_plugin_list() - else: - print(f"[Settings] Failed to enable plugin: {plugin_id}") - else: - # Check if other enabled plugins depend on this one - dependents = self.plugin_dependents.get(plugin_id, []) - enabled_dependents = [d for d in dependents if plugin_manager.is_plugin_enabled(d)] - - if enabled_dependents: - # Show warning - from PyQt6.QtWidgets import QMessageBox - - dep_names = [pid.split('.')[-1].replace('_', ' ').title() for pid in enabled_dependents] - msg = f"Cannot disable: This plugin is required by:\n\n" - msg += "\n".join(f" • {name}" for name in dep_names) - msg += "\n\nDisable those plugins first." - - QMessageBox.warning(None, "Dependency Warning", msg) - - # Recheck the box - self.plugin_checkboxes[plugin_id].setChecked(True) - return - - success = plugin_manager.disable_plugin(plugin_id) - if success: - print(f"[Settings] Disabled plugin: {plugin_id}") - self._refresh_plugin_list() - - def _get_missing_dependencies(self, plugin_id: str, plugin_manager) -> list: - """Get list of dependencies that need to be enabled.""" - deps = self.plugin_deps.get(plugin_id, []) - missing = [] - - for dep_id in deps: - if not plugin_manager.is_plugin_enabled(dep_id): - missing.append(dep_id) - - return missing - - def _refresh_plugin_list(self): - """Refresh the plugin list UI.""" - if not hasattr(self.overlay, 'plugin_manager'): - return - - plugin_manager = self.overlay.plugin_manager - - for plugin_id, cb in self.plugin_checkboxes.items(): - is_enabled = plugin_manager.is_plugin_enabled(plugin_id) - is_auto_enabled = plugin_manager.is_auto_enabled(plugin_id) if hasattr(plugin_manager, 'is_auto_enabled') else False - - cb.setChecked(is_enabled) - - if is_auto_enabled: - cb.setEnabled(False) - # Update text to show auto status - plugin_class = plugin_manager.get_all_discovered_plugins().get(plugin_id) - if plugin_class: - cb.setText(f"{plugin_class.name} (auto)") - else: - cb.setEnabled(True) - plugin_class = plugin_manager.get_all_discovered_plugins().get(plugin_id) - if plugin_class: - cb.setText(plugin_class.name) - - def _generate_dependency_report(self) -> str: - """Generate HTML dependency report.""" - if not hasattr(self.overlay, 'plugin_manager'): - return "

No plugin manager available

" - - plugin_manager = self.overlay.plugin_manager - all_plugins = plugin_manager.get_all_discovered_plugins() - - html = [""] - html.append("

📋 Plugin Dependency Report

") - html.append("
") - - # Summary section - total = len(all_plugins) - enabled = sum(1 for pid in all_plugins if plugin_manager.is_plugin_enabled(pid)) - html.append(f"

Total Plugins: {total} | Enabled: {enabled}

") - html.append("
") - - # Plugins with dependencies - html.append("

🔗 Plugins with Dependencies

") - html.append("
    ") - - for plugin_id, deps in sorted(self.plugin_deps.items()): - if deps: - plugin_class = all_plugins.get(plugin_id) - if plugin_class: - name = plugin_class.name - dep_names = [d.split('.')[-1].replace('_', ' ').title() for d in deps] - html.append(f"
  • {name} requires: {', '.join(dep_names)}
  • ") - - html.append("
") - html.append("
") - - # Plugins required by others - html.append("

⚠️ Plugins Required by Others

") - html.append("
    ") - - for plugin_id, dependents in sorted(self.plugin_dependents.items()): - if dependents: - plugin_class = all_plugins.get(plugin_id) - if plugin_class: - name = plugin_class.name - dep_names = [d.split('.')[-1].replace('_', ' ').title() for d in dependents] - html.append(f"
  • {name} is required by: {', '.join(dep_names)}
  • ") - - html.append("
") - html.append("
") - - # Dependency chain visualization - html.append("

🔄 Dependency Chains

") - html.append("
    ") - - for plugin_id, plugin_class in sorted(all_plugins.items(), key=lambda x: x[1].name): - chain = self._get_dependency_chain(plugin_id) - if len(chain) > 1: - chain_names = [all_plugins.get(pid, type('obj', (object,), {'name': pid})) .name for pid in chain] - html.append(f"
  • {' → '.join(chain_names)}
  • ") - - html.append("
") - html.append("") - - return "\n".join(html) - - def _get_dependency_chain(self, plugin_id: str, visited=None) -> list: - """Get the dependency chain for a plugin.""" - if visited is None: - visited = set() - - if plugin_id in visited: - return [plugin_id] # Circular dependency - - visited.add(plugin_id) - chain = [plugin_id] - - # Get what this plugin depends on - deps = self.plugin_deps.get(plugin_id, []) - for dep_id in deps: - if dep_id not in visited: - chain.extend(self._get_dependency_chain(dep_id, visited)) - - return chain - - def _enable_all_plugins(self): - """Enable all plugins with dependency resolution.""" - if not hasattr(self.overlay, 'plugin_manager'): - return - - plugin_manager = self.overlay.plugin_manager - all_plugins = plugin_manager.get_all_discovered_plugins() - - # Sort plugins so dependencies are enabled first - sorted_plugins = self._sort_plugins_by_dependencies(all_plugins) - - enabled_count = 0 - for plugin_id in sorted_plugins: - cb = self.plugin_checkboxes.get(plugin_id) - if cb: - cb.setChecked(True) - success = plugin_manager.enable_plugin(plugin_id) - if success: - enabled_count += 1 - - self._refresh_plugin_list() - print(f"[Settings] Enabled {enabled_count} plugins") - - def _sort_plugins_by_dependencies(self, all_plugins: dict) -> list: - """Sort plugins so dependencies come before dependents.""" - plugin_ids = list(all_plugins.keys()) - - # Build dependency graph - graph = {pid: set(self.plugin_deps.get(pid, [])) for pid in plugin_ids} - - # Topological sort - sorted_list = [] - visited = set() - temp_mark = set() - - def visit(pid): - if pid in temp_mark: - return # Circular dependency, skip - if pid in visited: - return - - temp_mark.add(pid) - for dep in graph.get(pid, set()): - if dep in graph: - visit(dep) - temp_mark.remove(pid) - visited.add(pid) - sorted_list.append(pid) - - for pid in plugin_ids: - visit(pid) - - return sorted_list - - def _disable_all_plugins(self): - """Disable all plugins in reverse dependency order.""" - if not hasattr(self.overlay, 'plugin_manager'): - return - - plugin_manager = self.overlay.plugin_manager - all_plugins = plugin_manager.get_all_discovered_plugins() - - # Sort so dependents are disabled first (reverse of enable order) - sorted_plugins = self._sort_plugins_by_dependencies(all_plugins) - sorted_plugins.reverse() - - disabled_count = 0 - for plugin_id in sorted_plugins: - cb = self.plugin_checkboxes.get(plugin_id) - if cb: - cb.setChecked(False) - success = plugin_manager.disable_plugin(plugin_id) - if success: - disabled_count += 1 - - self._refresh_plugin_list() - print(f"[Settings] Disabled {disabled_count} plugins") diff --git a/plugins/universal_search/__init__.py b/plugins/universal_search/__init__.py deleted file mode 100644 index 44d5b53..0000000 --- a/plugins/universal_search/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -""" -Universal Search Plugin for EU-Utility -""" - -from .plugin import UniversalSearchPlugin - -__all__ = ["UniversalSearchPlugin"] diff --git a/plugins/universal_search/__pycache__/__init__.cpython-312.pyc b/plugins/universal_search/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 8befb4f41eae2cedf385a5949b8652c31537668b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 278 zcmX@j%ge<81l8(&ne{;WF^B^LOi;#WDIjAyLkdF_LkeRGQx0P;Qxp>;Lke>`V-#~G zizaK823KfaW?5=cabk`_aB5;va)v@cPHB2(onk={2C%vKQb{fvV7%WVAOiR Pt#pA)xskny11JXoJeW(G diff --git a/plugins/universal_search/__pycache__/plugin.cpython-312.pyc b/plugins/universal_search/__pycache__/plugin.cpython-312.pyc deleted file mode 100644 index cafa0f9ace747fc2f18a9452fdae9dd449c77312..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26627 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 diff --git a/plugins/universal_search/plugin.py b/plugins/universal_search/plugin.py deleted file mode 100644 index 31f0f0c..0000000 --- a/plugins/universal_search/plugin.py +++ /dev/null @@ -1,600 +0,0 @@ -""" -EU-Utility - Universal Search Plugin - -Search across all Entropia Nexus entities - items, mobs, locations, blueprints, skills, etc. -""" - -import json -import webbrowser -from PyQt6.QtWidgets import ( - QWidget, QVBoxLayout, QHBoxLayout, - QLineEdit, QPushButton, QLabel, QComboBox, - QTableWidget, QTableWidgetItem, QHeaderView, - QTabWidget, QStackedWidget, QFrame -) -from PyQt6.QtCore import Qt, QThread, pyqtSignal - -from plugins.base_plugin import BasePlugin - - -class NexusEntityAPI: - """Client for Entropia Nexus Entity API.""" - - BASE_URL = "https://api.entropianexus.com" - - # Entity type to API endpoint mapping - ENDPOINTS = { - "Items": "/items", - "Weapons": "/weapons", - "Armors": "/armors", - "Blueprints": "/blueprints", - "Mobs": "/mobs", - "Locations": "/locations", - "Skills": "/skills", - "Materials": "/materials", - "Enhancers": "/enhancers", - "Medical Tools": "/medicaltools", - "Finders": "/finders", - "Excavators": "/excavators", - "Refiners": "/refiners", - "Vehicles": "/vehicles", - "Pets": "/pets", - "Decorations": "/decorations", - "Furniture": "/furniture", - "Storage": "/storagecontainers", - "Strongboxes": "/strongboxes", - "Teleporters": "/teleporters", - "Shops": "/shops", - "Vendors": "/vendors", - "Planets": "/planets", - "Areas": "/areas", - } - - @classmethod - def search_entities(cls, entity_type, query, limit=50, http_get_func=None): - """Search for entities of a specific type.""" - try: - endpoint = cls.ENDPOINTS.get(entity_type, "/items") - - # Build URL with query params - params = {'q': query, 'limit': limit, 'fuzzy': 'true'} - query_string = '&'.join(f"{k}={v}" for k, v in params.items()) - url = f"{cls.BASE_URL}{endpoint}?{query_string}" - - if http_get_func: - response = http_get_func( - url, - cache_ttl=300, # 5 minute cache - headers={'Accept': 'application/json', 'User-Agent': 'EU-Utility/1.0'} - ) - else: - # Fallback for standalone usage - import urllib.request - req = urllib.request.Request( - url, - headers={'Accept': 'application/json', 'User-Agent': 'EU-Utility/1.0'} - ) - with urllib.request.urlopen(req, timeout=15) as resp: - response = {'json': json.loads(resp.read().decode('utf-8'))} - - data = response.get('json') if response else None - return data if isinstance(data, list) else [] - - except Exception as e: - print(f"API Error ({entity_type}): {e}") - return [] - - @classmethod - def universal_search(cls, query, limit=30, http_get_func=None): - """Universal search across all entity types.""" - try: - params = {'query': query, 'limit': limit, 'fuzzy': 'true'} - query_string = '&'.join(f"{k}={v}" for k, v in params.items()) - url = f"{cls.BASE_URL}/search?{query_string}" - - if http_get_func: - response = http_get_func( - url, - cache_ttl=300, # 5 minute cache - headers={'Accept': 'application/json', 'User-Agent': 'EU-Utility/1.0'} - ) - else: - # Fallback for standalone usage - import urllib.request - req = urllib.request.Request( - url, - headers={'Accept': 'application/json', 'User-Agent': 'EU-Utility/1.0'} - ) - with urllib.request.urlopen(req, timeout=15) as resp: - response = {'json': json.loads(resp.read().decode('utf-8'))} - - data = response.get('json') if response else None - return data if isinstance(data, list) else [] - - except Exception as e: - print(f"Universal Search Error: {e}") - return [] - - @classmethod - def get_entity_url(cls, entity_type, entity_id_or_name): - """Get the web URL for an entity.""" - web_base = "https://www.entropianexus.com" - - # Map to web paths - web_paths = { - "Items": "items", - "Weapons": "items", - "Armors": "items", - "Blueprints": "blueprints", - "Mobs": "mobs", - "Locations": "locations", - "Skills": "skills", - "Materials": "items", - "Enhancers": "items", - "Medical Tools": "items", - "Finders": "items", - "Excavators": "items", - "Refiners": "items", - "Vehicles": "items", - "Pets": "items", - "Decorations": "items", - "Furniture": "items", - "Storage": "items", - "Strongboxes": "items", - "Teleporters": "locations", - "Shops": "locations", - "Vendors": "locations", - "Planets": "locations", - "Areas": "locations", - } - - path = web_paths.get(entity_type, "items") - return f"{web_base}/{path}/{entity_id_or_name}" - - -class UniversalSearchThread(QThread): - """Background thread for API searches.""" - results_ready = pyqtSignal(list, str) - error_occurred = pyqtSignal(str) - - def __init__(self, query, entity_type, universal=False, http_get_func=None): - super().__init__() - self.query = query - self.entity_type = entity_type - self.universal = universal - self.http_get_func = http_get_func - - def run(self): - """Perform API search.""" - try: - if self.universal: - results = NexusEntityAPI.universal_search(self.query, http_get_func=self.http_get_func) - else: - results = NexusEntityAPI.search_entities(self.entity_type, self.query, http_get_func=self.http_get_func) - - self.results_ready.emit(results, self.entity_type) - - except Exception as e: - self.error_occurred.emit(str(e)) - - -class UniversalSearchPlugin(BasePlugin): - """Universal search across all Nexus entities.""" - - name = "Universal Search" - version = "2.0.0" - author = "ImpulsiveFPS" - description = "Search items, mobs, locations, blueprints, skills, and more" - hotkey = "ctrl+shift+f" # F for Find - - def initialize(self): - """Setup the plugin.""" - self.search_thread = None - self.current_results = [] - self.current_entity_type = "Universal" - - def get_ui(self): - """Create plugin UI.""" - widget = QWidget() - layout = QVBoxLayout(widget) - layout.setSpacing(10) - - # Title - NO EMOJI - title = QLabel("Universal Search") - title.setStyleSheet("color: #4a9eff; font-size: 18px; font-weight: bold;") - layout.addWidget(title) - - # Search mode selector - mode_layout = QHBoxLayout() - mode_layout.addWidget(QLabel("Mode:")) - - self.search_mode = QComboBox() - self.search_mode.addItem("Universal (All Types)", "Universal") - self.search_mode.addItem("──────────────────", "separator") - - # Add all entity types - entity_types = [ - "Items", - "Weapons", - "Armors", - "Blueprints", - "Mobs", - "Locations", - "Skills", - "Materials", - "Enhancers", - "Medical Tools", - "Finders", - "Excavators", - "Refiners", - "Vehicles", - "Pets", - "Decorations", - "Furniture", - "Storage", - "Strongboxes", - "Teleporters", - "Shops", - "Vendors", - "Planets", - "Areas", - ] - - for etype in entity_types: - self.search_mode.addItem(f" {etype}", etype) - - self.search_mode.setStyleSheet(""" - QComboBox { - background-color: #444; - color: white; - padding: 8px; - border-radius: 4px; - min-width: 200px; - } - QComboBox::drop-down { - border: none; - } - """) - self.search_mode.currentIndexChanged.connect(self._on_mode_changed) - mode_layout.addWidget(self.search_mode) - mode_layout.addStretch() - - layout.addLayout(mode_layout) - - # Search bar - search_layout = QHBoxLayout() - - self.search_input = QLineEdit() - self.search_input.setPlaceholderText("Search for anything... (e.g., 'ArMatrix', 'Argonaut', 'Calypso')") - self.search_input.setStyleSheet(""" - QLineEdit { - background-color: #333; - color: white; - padding: 10px; - border: 2px solid #555; - border-radius: 4px; - font-size: 14px; - } - QLineEdit:focus { - border-color: #4a9eff; - } - """) - self.search_input.returnPressed.connect(self._do_search) - search_layout.addWidget(self.search_input, 1) - - search_btn = QPushButton("Search") - search_btn.setStyleSheet(""" - QPushButton { - background-color: #4a9eff; - color: white; - padding: 10px 20px; - border: none; - border-radius: 4px; - font-weight: bold; - font-size: 13px; - } - QPushButton:hover { - background-color: #5aafff; - } - """) - search_btn.clicked.connect(self._do_search) - search_layout.addWidget(search_btn) - - layout.addLayout(search_layout) - - # Status - self.status_label = QLabel("Ready to search") - self.status_label.setStyleSheet("color: #666; font-size: 11px;") - layout.addWidget(self.status_label) - - # Results table - self.results_table = QTableWidget() - self.results_table.setColumnCount(4) - self.results_table.setHorizontalHeaderLabels(["Name", "Type", "Details", "ID"]) - self.results_table.horizontalHeader().setStretchLastSection(False) - self.results_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch) - self.results_table.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed) - self.results_table.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeMode.Stretch) - self.results_table.horizontalHeader().setSectionResizeMode(3, QHeaderView.ResizeMode.Fixed) - self.results_table.setColumnWidth(1, 120) - self.results_table.setColumnWidth(3, 60) - self.results_table.verticalHeader().setVisible(False) - self.results_table.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows) - self.results_table.setStyleSheet(""" - QTableWidget { - background-color: #2a2a2a; - color: white; - border: 1px solid #444; - border-radius: 4px; - gridline-color: #333; - } - QTableWidget::item { - padding: 10px; - border-bottom: 1px solid #333; - } - QTableWidget::item:selected { - background-color: #4a9eff; - } - QTableWidget::item:hover { - background-color: #3a3a3a; - } - QHeaderView::section { - background-color: #333; - color: #aaa; - padding: 10px; - border: none; - font-weight: bold; - } - """) - self.results_table.cellDoubleClicked.connect(self._on_item_double_clicked) - self.results_table.setMaximumHeight(350) - self.results_table.setMinimumHeight(200) - layout.addWidget(self.results_table) - - # Action buttons - action_layout = QHBoxLayout() - - self.open_btn = QPushButton("Open Selected") - self.open_btn.setEnabled(False) - self.open_btn.setStyleSheet(""" - QPushButton { - background-color: #4a9eff; - color: white; - padding: 8px 16px; - border: none; - border-radius: 4px; - } - QPushButton:hover { - background-color: #5aafff; - } - QPushButton:disabled { - background-color: #444; - color: #666; - } - """) - self.open_btn.clicked.connect(self._open_selected) - action_layout.addWidget(self.open_btn) - - action_layout.addStretch() - - # Quick category buttons - quick_label = QLabel("Quick:") - quick_label.setStyleSheet("color: #666;") - action_layout.addWidget(quick_label) - - for category in ["Items", "Mobs", "Blueprints", "Locations"]: - btn = QPushButton(category) - btn.setStyleSheet(""" - QPushButton { - background-color: transparent; - color: #4a9eff; - border: 1px solid #4a9eff; - padding: 5px 10px; - border-radius: 3px; - } - QPushButton:hover { - background-color: #4a9eff; - color: white; - } - """) - btn.clicked.connect(lambda checked, c=category: self._quick_search(c)) - action_layout.addWidget(btn) - - layout.addLayout(action_layout) - - # Tips - tips = QLabel("Tip: Double-click result to open on Nexus website") - tips.setStyleSheet("color: #555; font-size: 10px;") - layout.addWidget(tips) - - layout.addStretch() - - return widget - - def _on_mode_changed(self): - """Handle search mode change.""" - data = self.search_mode.currentData() - if data == "separator": - # Reset to previous valid selection - self.search_mode.setCurrentIndex(0) - - def _do_search(self): - """Perform search.""" - query = self.search_input.text().strip() - if len(query) < 2: - self.status_label.setText("Enter at least 2 characters") - return - - entity_type = self.search_mode.currentData() - if entity_type == "separator": - entity_type = "Universal" - - self.current_entity_type = entity_type - universal = (entity_type == "Universal") - - # Clear previous results - self.results_table.setRowCount(0) - self.current_results = [] - self.open_btn.setEnabled(False) - self.status_label.setText(f"Searching for '{query}'...") - - # Start search thread with http_get function - self.search_thread = UniversalSearchThread( - query, entity_type, universal, - http_get_func=self.http_get - ) - self.search_thread.results_ready.connect(self._on_results) - self.search_thread.error_occurred.connect(self._on_error) - self.search_thread.start() - - def _quick_search(self, category): - """Quick search for a specific category.""" - # Set the category - index = self.search_mode.findData(category) - if index >= 0: - self.search_mode.setCurrentIndex(index) - - # If there's text in the search box, search immediately - if self.search_input.text().strip(): - self._do_search() - else: - self.search_input.setFocus() - self.status_label.setText(f"Selected: {category} - Enter search term") - - def _on_results(self, results, entity_type): - """Handle search results.""" - self.current_results = results - - if not results: - self.status_label.setText("No results found") - return - - # Populate table - self.results_table.setRowCount(len(results)) - - for row, item in enumerate(results): - # Extract data based on available fields - name = item.get('name', item.get('Name', 'Unknown')) - item_id = str(item.get('id', item.get('Id', ''))) - - # Determine type - if 'type' in item: - item_type = item['type'] - elif entity_type != "Universal": - item_type = entity_type - else: - # Try to infer from other fields - item_type = self._infer_type(item) - - # Build details string - details = self._build_details(item, item_type) - - # Set table items - self.results_table.setItem(row, 0, QTableWidgetItem(name)) - self.results_table.setItem(row, 1, QTableWidgetItem(item_type)) - self.results_table.setItem(row, 2, QTableWidgetItem(details)) - self.results_table.setItem(row, 3, QTableWidgetItem(item_id)) - - self.open_btn.setEnabled(True) - self.status_label.setText(f"Found {len(results)} results") - - def _infer_type(self, item): - """Infer entity type from item fields.""" - if 'damage' in item or 'range' in item: - return "Weapon" - elif 'protection' in item or 'durability' in item: - return "Armor" - elif 'hitpoints' in item: - return "Mob" - elif 'x' in item and 'y' in item: - return "Location" - elif 'qr' in item or 'click' in item: - return "Blueprint" - elif 'category' in item: - return item['category'] - else: - return "Item" - - def _build_details(self, item, item_type): - """Build details string based on item type.""" - details = [] - - if item_type in ["Weapon", "Weapons"]: - if 'damage' in item: - details.append(f"Dmg: {item['damage']}") - if 'range' in item: - details.append(f"Range: {item['range']}m") - if 'attacks' in item: - details.append(f"{item['attacks']} attacks") - - elif item_type in ["Armor", "Armors"]: - if 'protection' in item: - details.append(f"Prot: {item['protection']}") - if 'durability' in item: - details.append(f"Dur: {item['durability']}") - - elif item_type in ["Mob", "Mobs"]: - if 'hitpoints' in item: - details.append(f"HP: {item['hitpoints']}") - if 'damage' in item: - details.append(f"Dmg: {item['damage']}") - if 'threat' in item: - details.append(f"Threat: {item['threat']}") - - elif item_type in ["Blueprint", "Blueprints"]: - if 'qr' in item: - details.append(f"QR: {item['qr']}") - if 'click' in item: - details.append(f"Clicks: {item['click']}") - - elif item_type in ["Location", "Locations", "Teleporter", "Shop"]: - if 'planet' in item: - details.append(item['planet']) - if 'x' in item and 'y' in item: - details.append(f"[{item['x']}, {item['y']}]") - - elif item_type in ["Skill", "Skills"]: - if 'category' in item: - details.append(item['category']) - - # Add any other interesting fields - if 'level' in item: - details.append(f"Lvl: {item['level']}") - if 'weight' in item: - details.append(f"{item['weight']}kg") - - return " | ".join(details) if details else "" - - def _on_error(self, error): - """Handle search error.""" - self.status_label.setText(f"Error: {error}") - - def _on_item_double_clicked(self, row, column): - """Handle item double-click.""" - self._open_result(row) - - def _open_selected(self): - """Open selected result.""" - selected = self.results_table.selectedItems() - if selected: - row = selected[0].row() - self._open_result(row) - - def _open_result(self, row): - """Open result in browser.""" - if row < len(self.current_results): - item = self.current_results[row] - entity_id = item.get('id', item.get('Id', '')) - entity_name = item.get('name', item.get('Name', '')) - - # Use name for URL if available, otherwise ID - url_param = entity_name if entity_name else str(entity_id) - url = NexusEntityAPI.get_entity_url(self.current_entity_type, url_param) - - webbrowser.open(url) - - def on_hotkey(self): - """Focus search when hotkey pressed.""" - if hasattr(self, 'search_input'): - self.search_input.setFocus() - self.search_input.selectAll()