diff --git a/plugins/game_reader_test/__init__.py b/plugins/game_reader_test/__init__.py new file mode 100644 index 0000000..401c3cf --- /dev/null +++ b/plugins/game_reader_test/__init__.py @@ -0,0 +1,2 @@ +"""Game Reader Test Plugin.""" +from .plugin import GameReaderTestPlugin diff --git a/plugins/game_reader_test/plugin.py b/plugins/game_reader_test/plugin.py new file mode 100644 index 0000000..8032d31 --- /dev/null +++ b/plugins/game_reader_test/plugin.py @@ -0,0 +1,628 @@ +""" +EU-Utility - Game Reader Test Plugin + +Debug and test tool for OCR and game reading functionality. +Tests screen capture, OCR accuracy, and text extraction. +""" + +from PyQt6.QtWidgets import ( + QWidget, QVBoxLayout, QHBoxLayout, QTextEdit, + QLabel, QPushButton, QComboBox, QCheckBox, + QSpinBox, QGroupBox, QSplitter, QFrame, + QTabWidget, QLineEdit, QProgressBar, + QFileDialog, QMessageBox +) +from PyQt6.QtCore import Qt, QTimer, QThread, pyqtSignal +from PyQt6.QtGui import QPixmap, QImage, QColor + +from plugins.base_plugin import BasePlugin + + +class OCRTestThread(QThread): + """Background thread for OCR testing.""" + result_ready = pyqtSignal(dict) + progress_update = pyqtSignal(int, str) + + def __init__(self, region=None, backend='auto'): + super().__init__() + self.region = region + self.backend = backend + + def run(self): + """Run OCR test.""" + import time + results = { + 'success': False, + 'text': '', + 'backend_used': '', + 'processing_time': 0, + 'error': None + } + + try: + start_time = time.time() + self.progress_update.emit(10, "Capturing screen...") + + # Capture screen + from PIL import Image, ImageGrab + if self.region: + screenshot = ImageGrab.grab(bbox=self.region) + else: + screenshot = ImageGrab.grab() + + self.progress_update.emit(30, "Running OCR...") + + # Try OCR backends + text = "" + backend_used = "none" + + if self.backend in ('auto', 'easyocr'): + try: + import easyocr + self.progress_update.emit(50, "Loading EasyOCR...") + reader = easyocr.Reader(['en'], gpu=False, verbose=False) + self.progress_update.emit(70, "Processing with EasyOCR...") + ocr_result = reader.readtext( + screenshot, + detail=0, + paragraph=True + ) + text = '\n'.join(ocr_result) + backend_used = "easyocr" + except Exception as e: + if self.backend == 'easyocr': + raise e + + if not text and self.backend in ('auto', 'tesseract'): + try: + import pytesseract + self.progress_update.emit(70, "Processing with Tesseract...") + text = pytesseract.image_to_string(screenshot) + backend_used = "tesseract" + except Exception as e: + if self.backend == 'tesseract': + raise e + + if not text and self.backend in ('auto', 'paddle'): + try: + from paddleocr import PaddleOCR + self.progress_update.emit(70, "Processing with PaddleOCR...") + ocr = PaddleOCR(use_angle_cls=True, lang='en', show_log=False) + result = ocr.ocr(screenshot, cls=True) + texts = [line[1][0] for line in result[0]] + text = '\n'.join(texts) + backend_used = "paddleocr" + except Exception as e: + if self.backend == 'paddle': + raise e + + processing_time = time.time() - start_time + + results.update({ + 'success': True, + 'text': text or "No text detected", + 'backend_used': backend_used, + 'processing_time': processing_time + }) + + self.progress_update.emit(100, "Complete!") + + except Exception as e: + results['error'] = str(e) + self.progress_update.emit(100, f"Error: {e}") + + self.result_ready.emit(results) + + +class GameReaderTestPlugin(BasePlugin): + """Test and debug tool for game reading/OCR functionality.""" + + name = "Game Reader Test" + version = "1.0.0" + author = "EU-Utility" + description = "Debug tool for testing OCR and screen reading" + + def __init__(self, overlay_window, config): + super().__init__(overlay_window, config) + self.test_history = [] + self.max_history = 50 + self.ocr_thread = None + + def initialize(self): + """Initialize plugin.""" + self.log_info("Game Reader Test initialized") + + def get_ui(self): + """Create plugin UI.""" + widget = QWidget() + layout = QVBoxLayout(widget) + layout.setSpacing(12) + + # Header + header = QLabel("📷 Game Reader Test & Debug Tool") + header.setStyleSheet("font-size: 16px; font-weight: bold; color: #ff8c42;") + layout.addWidget(header) + + # Create tabs + tabs = QTabWidget() + + # Tab 1: Quick Test + tabs.addTab(self._create_quick_test_tab(), "Quick Test") + + # Tab 2: Region Test + tabs.addTab(self._create_region_test_tab(), "Region Test") + + # Tab 3: History + tabs.addTab(self._create_history_tab(), "History") + + # Tab 4: Calibration + tabs.addTab(self._create_calibration_tab(), "Calibration") + + layout.addWidget(tabs, 1) + + # Status bar + status_frame = QFrame() + status_frame.setStyleSheet("background-color: #1a1f2e; border-radius: 6px; padding: 8px;") + status_layout = QHBoxLayout(status_frame) + + self.status_label = QLabel("Ready - Select a tab to begin testing") + self.status_label.setStyleSheet("color: #4ecdc4;") + status_layout.addWidget(self.status_label) + + layout.addWidget(status_frame) + + return widget + + def _create_quick_test_tab(self): + """Create quick test tab.""" + tab = QWidget() + layout = QVBoxLayout(tab) + + # Info + info = QLabel("Quick OCR Test - Captures full screen and extracts text") + info.setStyleSheet("color: #888;") + layout.addWidget(info) + + # Backend selection + backend_layout = QHBoxLayout() + backend_layout.addWidget(QLabel("OCR Backend:")) + self.backend_combo = QComboBox() + self.backend_combo.addItems(["Auto (try all)", "EasyOCR", "Tesseract", "PaddleOCR"]) + backend_layout.addWidget(self.backend_combo) + backend_layout.addStretch() + layout.addLayout(backend_layout) + + # Progress bar + self.progress_bar = QProgressBar() + self.progress_bar.setRange(0, 100) + self.progress_bar.setValue(0) + self.progress_bar.setStyleSheet(""" + QProgressBar { + border: 1px solid #333; + border-radius: 4px; + text-align: center; + } + QProgressBar::chunk { + background-color: #ff8c42; + } + """) + layout.addWidget(self.progress_bar) + + # Test button + self.test_btn = QPushButton("▶ Run OCR Test") + self.test_btn.setStyleSheet(""" + QPushButton { + background-color: #ff8c42; + color: #141f23; + font-weight: bold; + padding: 12px; + font-size: 14px; + } + QPushButton:hover { + background-color: #ffa05c; + } + """) + self.test_btn.clicked.connect(self._run_quick_test) + layout.addWidget(self.test_btn) + + # Results + results_group = QGroupBox("OCR Results") + results_layout = QVBoxLayout(results_group) + + self.results_text = QTextEdit() + self.results_text.setReadOnly(True) + self.results_text.setPlaceholderText("OCR results will appear here...") + self.results_text.setStyleSheet(""" + QTextEdit { + background-color: #0d1117; + color: #c9d1d9; + font-family: Consolas, monospace; + font-size: 12px; + } + """) + results_layout.addWidget(self.results_text) + + # Stats + self.stats_label = QLabel("Backend: - | Time: - | Status: Waiting") + self.stats_label.setStyleSheet("color: #888;") + results_layout.addWidget(self.stats_label) + + layout.addWidget(results_group) + + # Save buttons + btn_layout = QHBoxLayout() + + save_text_btn = QPushButton("💾 Save Text") + save_text_btn.clicked.connect(self._save_text) + btn_layout.addWidget(save_text_btn) + + copy_btn = QPushButton("📋 Copy to Clipboard") + copy_btn.clicked.connect(self._copy_to_clipboard) + btn_layout.addWidget(copy_btn) + + clear_btn = QPushButton("🗑 Clear") + clear_btn.clicked.connect(self._clear_results) + btn_layout.addWidget(clear_btn) + + btn_layout.addStretch() + layout.addLayout(btn_layout) + + layout.addStretch() + return tab + + def _create_region_test_tab(self): + """Create region test tab.""" + tab = QWidget() + layout = QVBoxLayout(tab) + + info = QLabel("Test OCR on specific screen region") + info.setStyleSheet("color: #888;") + layout.addWidget(info) + + # Region input + region_group = QGroupBox("Screen Region (pixels)") + region_layout = QHBoxLayout(region_group) + + self.x_input = QSpinBox() + self.x_input.setRange(0, 10000) + self.x_input.setValue(100) + region_layout.addWidget(QLabel("X:")) + region_layout.addWidget(self.x_input) + + self.y_input = QSpinBox() + self.y_input.setRange(0, 10000) + self.y_input.setValue(100) + region_layout.addWidget(QLabel("Y:")) + region_layout.addWidget(self.y_input) + + self.w_input = QSpinBox() + self.w_input.setRange(100, 10000) + self.w_input.setValue(400) + region_layout.addWidget(QLabel("Width:")) + region_layout.addWidget(self.w_input) + + self.h_input = QSpinBox() + self.h_input.setRange(100, 10000) + self.h_input.setValue(300) + region_layout.addWidget(QLabel("Height:")) + region_layout.addWidget(self.h_input) + + layout.addWidget(region_group) + + # Presets + preset_layout = QHBoxLayout() + preset_layout.addWidget(QLabel("Quick Presets:")) + + presets = [ + ("Chat Window", 10, 800, 600, 200), + ("Skills Window", 100, 100, 500, 400), + ("Inventory", 1200, 200, 600, 500), + ("Mission Tracker", 1600, 100, 300, 600), + ] + + for name, x, y, w, h in presets: + btn = QPushButton(name) + btn.clicked.connect(lambda checked, px=x, py=y, pw=w, ph=h: self._set_region(px, py, pw, ph)) + preset_layout.addWidget(btn) + + preset_layout.addStretch() + layout.addLayout(preset_layout) + + # Test button + region_test_btn = QPushButton("▶ Test Region OCR") + region_test_btn.setStyleSheet(""" + QPushButton { + background-color: #4ecdc4; + color: #141f23; + font-weight: bold; + padding: 10px; + } + """) + region_test_btn.clicked.connect(self._run_region_test) + layout.addWidget(region_test_btn) + + # Results + self.region_results = QTextEdit() + self.region_results.setReadOnly(True) + self.region_results.setPlaceholderText("Region OCR results will appear here...") + self.region_results.setStyleSheet(""" + QTextEdit { + background-color: #0d1117; + color: #c9d1d9; + font-family: Consolas, monospace; + } + """) + layout.addWidget(self.region_results) + + layout.addStretch() + return tab + + def _create_history_tab(self): + """Create history tab.""" + tab = QWidget() + layout = QVBoxLayout(tab) + + info = QLabel("History of OCR tests") + info.setStyleSheet("color: #888;") + layout.addWidget(info) + + self.history_text = QTextEdit() + self.history_text.setReadOnly(True) + self.history_text.setStyleSheet(""" + QTextEdit { + background-color: #0d1117; + color: #c9d1d9; + } + """) + layout.addWidget(self.history_text) + + # Controls + btn_layout = QHBoxLayout() + + refresh_btn = QPushButton("🔄 Refresh") + refresh_btn.clicked.connect(self._update_history) + btn_layout.addWidget(refresh_btn) + + clear_hist_btn = QPushButton("🗑 Clear History") + clear_hist_btn.clicked.connect(self._clear_history) + btn_layout.addWidget(clear_hist_btn) + + btn_layout.addStretch() + layout.addLayout(btn_layout) + + return tab + + def _create_calibration_tab(self): + """Create calibration tab.""" + tab = QWidget() + layout = QVBoxLayout(tab) + + info = QLabel("Calibration tools for optimizing OCR accuracy") + info.setStyleSheet("color: #888;") + layout.addWidget(info) + + # DPI awareness + dpi_group = QGroupBox("Display Settings") + dpi_layout = QVBoxLayout(dpi_group) + + self.dpi_label = QLabel("Detecting display DPI...") + dpi_layout.addWidget(self.dpi_label) + + detect_dpi_btn = QPushButton("Detect Display Settings") + detect_dpi_btn.clicked.connect(self._detect_display_settings) + dpi_layout.addWidget(detect_dpi_btn) + + layout.addWidget(dpi_group) + + # Backend status + backend_group = QGroupBox("OCR Backend Status") + backend_layout = QVBoxLayout(backend_group) + + self.backend_status = QTextEdit() + self.backend_status.setReadOnly(True) + self.backend_status.setMaximumHeight(150) + self._check_backends() + backend_layout.addWidget(self.backend_status) + + layout.addWidget(backend_group) + + # Tips + tips_group = QGroupBox("Tips for Best Results") + tips_layout = QVBoxLayout(tips_group) + + tips_text = QLabel(""" + • Make sure text is clearly visible and not blurry + • Use region selection to focus on specific text areas + • Higher resolution screens work better for OCR + • Close other windows to reduce background noise + • Ensure good contrast between text and background + """) + tips_text.setWordWrap(True) + tips_text.setStyleSheet("color: #aaa;") + tips_layout.addWidget(tips_text) + + layout.addWidget(tips_group) + + layout.addStretch() + return tab + + def _set_region(self, x, y, w, h): + """Set region values.""" + self.x_input.setValue(x) + self.y_input.setValue(y) + self.w_input.setValue(w) + self.h_input.setValue(h) + + def _run_quick_test(self): + """Run quick OCR test.""" + if self.ocr_thread and self.ocr_thread.isRunning(): + return + + backend_map = { + 0: 'auto', + 1: 'easyocr', + 2: 'tesseract', + 3: 'paddle' + } + backend = backend_map.get(self.backend_combo.currentIndex(), 'auto') + + self.test_btn.setEnabled(False) + self.test_btn.setText("⏳ Running...") + self.progress_bar.setValue(0) + + self.ocr_thread = OCRTestThread(backend=backend) + self.ocr_thread.progress_update.connect(self._update_progress) + self.ocr_thread.result_ready.connect(self._on_ocr_complete) + self.ocr_thread.start() + + def _update_progress(self, value, message): + """Update progress bar.""" + self.progress_bar.setValue(value) + self.status_label.setText(message) + + def _on_ocr_complete(self, results): + """Handle OCR completion.""" + self.test_btn.setEnabled(True) + self.test_btn.setText("▶ Run OCR Test") + + if results['success']: + self.results_text.setPlainText(results['text']) + self.stats_label.setText( + f"Backend: {results['backend_used']} | " + f"Time: {results['processing_time']:.2f}s | " + f"Status: ✅ Success" + ) + + # Add to history + self._add_to_history(results) + else: + self.results_text.setPlainText(f"Error: {results.get('error', 'Unknown error')}") + self.stats_label.setText(f"Backend: {results.get('backend_used', '-')} | Status: ❌ Failed") + + def _run_region_test(self): + """Run region OCR test.""" + region = ( + self.x_input.value(), + self.y_input.value(), + self.w_input.value(), + self.h_input.value() + ) + + self.region_results.setPlainText("Running OCR on region...") + + # Use api's ocr_capture if available + try: + result = self.ocr_capture(region=region) + self.region_results.setPlainText(result.get('text', 'No text detected')) + except Exception as e: + self.region_results.setPlainText(f"Error: {e}") + + def _save_text(self): + """Save OCR text to file.""" + text = self.results_text.toPlainText() + if not text: + QMessageBox.warning(None, "Save Error", "No text to save!") + return + + file_path, _ = QFileDialog.getSaveFileName( + None, "Save OCR Text", "ocr_result.txt", "Text Files (*.txt)" + ) + if file_path: + with open(file_path, 'w', encoding='utf-8') as f: + f.write(text) + self.status_label.setText(f"Saved to {file_path}") + + def _copy_to_clipboard(self): + """Copy text to clipboard.""" + text = self.results_text.toPlainText() + if text: + self.copy_to_clipboard(text) + self.status_label.setText("Copied to clipboard!") + + def _clear_results(self): + """Clear results.""" + self.results_text.clear() + self.stats_label.setText("Backend: - | Time: - | Status: Waiting") + self.progress_bar.setValue(0) + + def _add_to_history(self, results): + """Add result to history.""" + from datetime import datetime + entry = { + 'time': datetime.now().strftime("%H:%M:%S"), + 'backend': results['backend_used'], + 'time_taken': results['processing_time'], + 'text_preview': results['text'][:100] + "..." if len(results['text']) > 100 else results['text'] + } + self.test_history.insert(0, entry) + if len(self.test_history) > self.max_history: + self.test_history = self.test_history[:self.max_history] + self._update_history() + + def _update_history(self): + """Update history display.""" + lines = [] + for entry in self.test_history: + lines.append(f"[{entry['time']}] {entry['backend']} ({entry['time_taken']:.2f}s)") + lines.append(f" {entry['text_preview']}") + lines.append("") + self.history_text.setPlainText('\n'.join(lines) if lines else "No history yet") + + def _clear_history(self): + """Clear history.""" + self.test_history.clear() + self._update_history() + + def _detect_display_settings(self): + """Detect display settings.""" + try: + from PyQt6.QtWidgets import QApplication + from PyQt6.QtGui import QScreen + + app = QApplication.instance() + if app: + screens = app.screens() + info = [] + for i, screen in enumerate(screens): + geo = screen.geometry() + dpi = screen.logicalDotsPerInch() + info.append(f"Screen {i+1}: {geo.width()}x{geo.height()} @ {dpi:.0f} DPI") + self.dpi_label.setText('\n'.join(info)) + except Exception as e: + self.dpi_label.setText(f"Error: {e}") + + def _check_backends(self): + """Check OCR backend availability.""" + statuses = [] + + try: + import easyocr + statuses.append("✅ EasyOCR - Available") + except ImportError: + statuses.append("❌ EasyOCR - Not installed (pip install easyocr)") + + try: + import pytesseract + statuses.append("✅ Tesseract - Available") + except ImportError: + statuses.append("❌ Tesseract - Not installed (pip install pytesseract)") + + try: + from paddleocr import PaddleOCR + statuses.append("✅ PaddleOCR - Available") + except ImportError: + statuses.append("❌ PaddleOCR - Not installed (pip install paddleocr)") + + try: + from PIL import ImageGrab + statuses.append("✅ Screen Capture - Available") + except ImportError: + statuses.append("❌ Screen Capture - PIL not available") + + self.backend_status.setPlainText('\n'.join(statuses)) + + def shutdown(self): + """Clean up.""" + if self.ocr_thread and self.ocr_thread.isRunning(): + self.ocr_thread.wait(1000) + super().shutdown() diff --git a/plugins/log_parser_test/__init__.py b/plugins/log_parser_test/__init__.py new file mode 100644 index 0000000..eeaca4c --- /dev/null +++ b/plugins/log_parser_test/__init__.py @@ -0,0 +1,2 @@ +"""Log Parser Test Plugin.""" +from .plugin import LogParserTestPlugin diff --git a/plugins/log_parser_test/plugin.py b/plugins/log_parser_test/plugin.py new file mode 100644 index 0000000..c66199e --- /dev/null +++ b/plugins/log_parser_test/plugin.py @@ -0,0 +1,366 @@ +""" +EU-Utility - Log Parser Test Plugin + +Debug and test tool for the Log Reader core service. +Monitors log parsing in real-time with detailed output. +""" + +from PyQt6.QtWidgets import ( + QWidget, QVBoxLayout, QHBoxLayout, QTextEdit, + QLabel, QPushButton, QComboBox, QCheckBox, + QSpinBox, QGroupBox, QSplitter, QFrame, + QTableWidget, QTableWidgetItem, QHeaderView +) +from PyQt6.QtCore import Qt, QTimer +from PyQt6.QtGui import QColor, QFont + +from plugins.base_plugin import BasePlugin +from core.event_bus import SkillGainEvent, LootEvent, DamageEvent, GlobalEvent + + +class LogParserTestPlugin(BasePlugin): + """Test and debug tool for log parsing functionality.""" + + name = "Log Parser Test" + version = "1.0.0" + author = "EU-Utility" + description = "Debug tool for testing log parsing and event detection" + + def __init__(self, overlay_window, config): + super().__init__(overlay_window, config) + self.event_counts = { + 'skill_gain': 0, + 'loot': 0, + 'global': 0, + 'damage': 0, + 'damage_taken': 0, + 'heal': 0, + 'mission_complete': 0, + 'tier_increase': 0, + 'enhancer_break': 0, + 'unknown': 0 + } + self.recent_events = [] + self.max_events = 100 + self.auto_scroll = True + + def initialize(self): + """Initialize plugin and subscribe to events.""" + # Subscribe to all event types for testing + self.subscribe_typed(SkillGainEvent, self._on_skill_gain) + self.subscribe_typed(LootEvent, self._on_loot) + self.subscribe_typed(DamageEvent, self._on_damage) + self.subscribe_typed(GlobalEvent, self._on_global) + + # Also subscribe to raw log events via API + api = self._get_api() + if api: + api.subscribe('log_event', self._on_raw_log_event) + + self.log_info("Log Parser Test initialized - monitoring events...") + + def _get_api(self): + """Get PluginAPI instance.""" + try: + from core.plugin_api import get_api + return get_api() + except: + return None + + def _on_skill_gain(self, event: SkillGainEvent): + """Handle skill gain events.""" + self.event_counts['skill_gain'] += 1 + self._add_event("Skill Gain", f"{event.skill_name}: +{event.points} pts", "#4ecdc4") + + def _on_loot(self, event: LootEvent): + """Handle loot events.""" + self.event_counts['loot'] += 1 + value_str = f" ({event.value:.2f} PED)" if event.value else "" + self._add_event("Loot", f"{event.item_name} x{event.quantity}{value_str}", "#ff8c42") + + def _on_damage(self, event: DamageEvent): + """Handle damage events.""" + if event.is_critical: + self.event_counts['damage'] += 1 + self._add_event("Damage", f"{event.amount} damage ({event.damage_type})", "#ff6b6b") + else: + self.event_counts['damage_taken'] += 1 + self._add_event("Damage Taken", f"{event.amount} damage", "#ff4757") + + def _on_global(self, event: GlobalEvent): + """Handle global events.""" + self.event_counts['global'] += 1 + self._add_event("Global", f"{event.player} found {event.item} worth {event.value} PED!", "#ffd93d") + + def _on_raw_log_event(self, event_data): + """Handle raw log events.""" + event_type = event_data.get('event_type', 'unknown') + self.event_counts[event_type] = self.event_counts.get(event_type, 0) + 1 + + def _add_event(self, event_type: str, details: str, color: str): + """Add event to recent events list.""" + from datetime import datetime + timestamp = datetime.now().strftime("%H:%M:%S") + + self.recent_events.insert(0, { + 'time': timestamp, + 'type': event_type, + 'details': details, + 'color': color + }) + + # Trim to max + if len(self.recent_events) > self.max_events: + self.recent_events = self.recent_events[:self.max_events] + + # Update UI if available + if hasattr(self, 'events_table'): + self._update_events_table() + + def get_ui(self): + """Create plugin UI.""" + widget = QWidget() + layout = QVBoxLayout(widget) + layout.setSpacing(12) + + # Header + header = QLabel("📊 Log Parser Test & Debug Tool") + header.setStyleSheet("font-size: 16px; font-weight: bold; color: #ff8c42;") + layout.addWidget(header) + + # Status bar + status_frame = QFrame() + status_frame.setStyleSheet("background-color: #1a1f2e; border-radius: 6px; padding: 8px;") + status_layout = QHBoxLayout(status_frame) + + self.status_label = QLabel("Status: Monitoring...") + self.status_label.setStyleSheet("color: #4ecdc4;") + status_layout.addWidget(self.status_label) + status_layout.addStretch() + + self.log_path_label = QLabel() + self._update_log_path() + self.log_path_label.setStyleSheet("color: #666;") + status_layout.addWidget(self.log_path_label) + + layout.addWidget(status_frame) + + # Event counters + counters_group = QGroupBox("Event Counters") + counters_layout = QHBoxLayout(counters_group) + + self.counter_labels = {} + for event_type, color in [ + ('skill_gain', '#4ecdc4'), + ('loot', '#ff8c42'), + ('global', '#ffd93d'), + ('damage', '#ff6b6b'), + ('damage_taken', '#ff4757') + ]: + frame = QFrame() + frame.setStyleSheet(f"background-color: #1a1f2e; border-left: 3px solid {color}; padding: 8px;") + frame_layout = QVBoxLayout(frame) + + name_label = QLabel(event_type.replace('_', ' ').title()) + name_label.setStyleSheet("color: #888; font-size: 10px;") + frame_layout.addWidget(name_label) + + count_label = QLabel("0") + count_label.setStyleSheet(f"color: {color}; font-size: 20px; font-weight: bold;") + frame_layout.addWidget(count_label) + + self.counter_labels[event_type] = count_label + counters_layout.addWidget(frame) + + layout.addWidget(counters_group) + + # Recent events table + events_group = QGroupBox("Recent Events (last 100)") + events_layout = QVBoxLayout(events_group) + + self.events_table = QTableWidget() + self.events_table.setColumnCount(3) + self.events_table.setHorizontalHeaderLabels(["Time", "Type", "Details"]) + self.events_table.horizontalHeader().setStretchLastSection(True) + self.events_table.setStyleSheet(""" + QTableWidget { + background-color: #141f23; + border: 1px solid #333; + } + QTableWidget::item { + padding: 6px; + } + """) + events_layout.addWidget(self.events_table) + + # Auto-scroll checkbox + self.auto_scroll_cb = QCheckBox("Auto-scroll to new events") + self.auto_scroll_cb.setChecked(True) + events_layout.addWidget(self.auto_scroll_cb) + + layout.addWidget(events_group, 1) # Stretch factor 1 + + # Test controls + controls_group = QGroupBox("Test Controls") + controls_layout = QHBoxLayout(controls_group) + + # Simulate event buttons + btn_layout = QHBoxLayout() + + test_skill_btn = QPushButton("Test: Skill Gain") + test_skill_btn.clicked.connect(self._simulate_skill_gain) + btn_layout.addWidget(test_skill_btn) + + test_loot_btn = QPushButton("Test: Loot") + test_loot_btn.clicked.connect(self._simulate_loot) + btn_layout.addWidget(test_loot_btn) + + test_damage_btn = QPushButton("Test: Damage") + test_damage_btn.clicked.connect(self._simulate_damage) + btn_layout.addWidget(test_damage_btn) + + clear_btn = QPushButton("Clear Events") + clear_btn.clicked.connect(self._clear_events) + btn_layout.addWidget(clear_btn) + + controls_layout.addLayout(btn_layout) + controls_layout.addStretch() + + layout.addWidget(controls_group) + + # Raw log view + raw_group = QGroupBox("Raw Log Lines (last 50)") + raw_layout = QVBoxLayout(raw_group) + + self.raw_log_text = QTextEdit() + self.raw_log_text.setReadOnly(True) + self.raw_log_text.setStyleSheet(""" + QTextEdit { + background-color: #0d1117; + color: #c9d1d9; + font-family: Consolas, monospace; + font-size: 11px; + } + """) + raw_layout.addWidget(self.raw_log_text) + + layout.addWidget(raw_group) + + # Start update timer + self.update_timer = QTimer() + self.update_timer.timeout.connect(self._update_counters) + self.update_timer.start(1000) # Update every second + + # Subscribe to raw log lines + self.read_log(lines=50) # Pre-fill with recent log + + return widget + + def _update_log_path(self): + """Update log path display.""" + try: + from core.log_reader import LogReader + reader = LogReader() + if reader.log_path: + self.log_path_label.setText(f"Log: {reader.log_path}") + else: + self.log_path_label.setText("Log: Not found") + except Exception as e: + self.log_path_label.setText(f"Log: Error - {e}") + + def _update_counters(self): + """Update counter displays.""" + for event_type, label in self.counter_labels.items(): + count = self.event_counts.get(event_type, 0) + label.setText(str(count)) + + def _update_events_table(self): + """Update events table with recent events.""" + self.events_table.setRowCount(len(self.recent_events)) + + for i, event in enumerate(self.recent_events): + # Time + time_item = QTableWidgetItem(event['time']) + time_item.setForeground(QColor("#888")) + self.events_table.setItem(i, 0, time_item) + + # Type + type_item = QTableWidgetItem(event['type']) + type_item.setForeground(QColor(event['color'])) + type_item.setFont(QFont("Segoe UI", weight=QFont.Weight.Bold)) + self.events_table.setItem(i, 1, type_item) + + # Details + details_item = QTableWidgetItem(event['details']) + details_item.setForeground(QColor("#c9d1d9")) + self.events_table.setItem(i, 2, details_item) + + # Auto-scroll + if self.auto_scroll_cb.isChecked() and self.recent_events: + self.events_table.scrollToTop() + + def _simulate_skill_gain(self): + """Simulate a skill gain event for testing.""" + from datetime import datetime + event = SkillGainEvent( + timestamp=datetime.now(), + skill_name="Rifle", + points=0.1234, + new_value=1234.56 + ) + self._on_skill_gain(event) + self.log_info("Simulated skill gain event") + + def _simulate_loot(self): + """Simulate a loot event for testing.""" + from datetime import datetime + event = LootEvent( + timestamp=datetime.now(), + item_name="Shrapnel", + quantity=100, + value=1.0, + mob_name="Atrox" + ) + self._on_loot(event) + self.log_info("Simulated loot event") + + def _simulate_damage(self): + """Simulate a damage event for testing.""" + from datetime import datetime + event = DamageEvent( + timestamp=datetime.now(), + amount=45, + damage_type="Impact", + is_critical=True + ) + self._on_damage(event) + self.log_info("Simulated damage event") + + def _clear_events(self): + """Clear all events.""" + self.recent_events.clear() + for key in self.event_counts: + self.event_counts[key] = 0 + self._update_events_table() + self._update_counters() + self.log_info("Cleared all events") + + def read_log(self, lines=50): + """Read recent log lines.""" + try: + log_lines = self.read_log(lines=lines) + if log_lines: + self.raw_log_text.setPlainText('\n'.join(log_lines)) + except Exception as e: + self.raw_log_text.setPlainText(f"Error reading log: {e}") + + def on_show(self): + """Called when plugin is shown.""" + self._update_events_table() + self._update_counters() + + def shutdown(self): + """Clean up.""" + if hasattr(self, 'update_timer'): + self.update_timer.stop() + super().shutdown()