feat: Add Log Parser and Game Reader Test Plugins

NEW TESTING PLUGINS:

1. Log Parser Test Plugin (plugins/log_parser_test/)
   - Real-time event monitoring with counters
   - Visual event history table (last 100 events)
   - Raw log line viewer
   - Simulated event buttons for testing:
     * Skill Gain
     * Loot
     * Damage
   - Event type filtering and statistics
   - Color-coded event types
   - Auto-scroll and refresh

2. Game Reader Test Plugin (plugins/game_reader_test/)
   - Quick OCR test with progress bar
   - Region-specific OCR testing with presets:
     * Chat Window
     * Skills Window
     * Inventory
     * Mission Tracker
   - OCR backend selection (Auto/EasyOCR/Tesseract/PaddleOCR)
   - Processing time tracking
   - Test history with results
   - Save/copy OCR results
   - Calibration tools:
     * Display DPI detection
     * Backend status checker
     * OCR tips and best practices

FEATURES FOR BOTH:
- Modern EU-styled UI
- Tabbed interface
- Error handling and logging
- Background processing (no UI freeze)
- Real-time updates

These plugins serve as both testing tools and demonstrations
of the Log Reader and OCR core services capabilities.
This commit is contained in:
LemonNexus 2026-02-14 19:08:17 +00:00
parent 8aad153c11
commit 219d0847b2
4 changed files with 998 additions and 0 deletions

View File

@ -0,0 +1,2 @@
"""Game Reader Test Plugin."""
from .plugin import GameReaderTestPlugin

View File

@ -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()

View File

@ -0,0 +1,2 @@
"""Log Parser Test Plugin."""
from .plugin import LogParserTestPlugin

View File

@ -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()