843 lines
28 KiB
Python
843 lines
28 KiB
Python
"""
|
|
Lemontropia Suite - Gallery Dialog
|
|
Browse and manage screenshots captured during hunting sessions.
|
|
"""
|
|
|
|
import os
|
|
import json
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
from typing import Optional, List, Dict, Any
|
|
|
|
# PIL is optional - screenshots won't work without it but gallery can still view
|
|
try:
|
|
from PIL import Image, ImageGrab
|
|
PIL_AVAILABLE = True
|
|
except ImportError:
|
|
PIL_AVAILABLE = False
|
|
Image = None
|
|
ImageGrab = None
|
|
|
|
from PyQt6.QtWidgets import (
|
|
QDialog, QVBoxLayout, QHBoxLayout, QFormLayout,
|
|
QLabel, QPushButton, QListWidget, QListWidgetItem,
|
|
QSplitter, QWidget, QGroupBox, QComboBox,
|
|
QMessageBox, QFileDialog, QScrollArea, QFrame,
|
|
QSizePolicy, QGridLayout
|
|
)
|
|
from PyQt6.QtCore import Qt, pyqtSignal, QSize
|
|
from PyQt6.QtGui import QPixmap, QImage, QColor
|
|
|
|
from core.database import DatabaseManager
|
|
|
|
|
|
class ImageLabel(QLabel):
|
|
"""Custom label for displaying images with click handling."""
|
|
|
|
clicked = pyqtSignal()
|
|
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
self.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
self.setStyleSheet("background-color: #252525; border: 1px solid #444;")
|
|
self.setMinimumSize(400, 300)
|
|
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
|
|
|
def mousePressEvent(self, event):
|
|
self.clicked.emit()
|
|
|
|
|
|
class GalleryDialog(QDialog):
|
|
"""
|
|
Dialog for viewing and managing captured screenshots.
|
|
|
|
Features:
|
|
- Browse all screenshots with thumbnails
|
|
- Filter by type (global, hof, all)
|
|
- View full-size image with metadata
|
|
- Delete screenshots
|
|
- Open in external viewer
|
|
"""
|
|
|
|
screenshot_deleted = pyqtSignal(int) # Emits screenshot_id when deleted
|
|
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
self.setWindowTitle("Screenshot Gallery")
|
|
self.setMinimumSize(1400, 900)
|
|
self.resize(1600, 1000)
|
|
|
|
# Initialize database
|
|
self.db = DatabaseManager()
|
|
|
|
# Ensure screenshots directory exists - default to Documents/Entropia Universe/Screenshots
|
|
self.screenshots_dir = Path.home() / "Documents" / "Entropia Universe" / "Screenshots"
|
|
self.screenshots_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
# State
|
|
self.current_screenshot_id: Optional[int] = None
|
|
self.screenshots_data: List[Dict[str, Any]] = []
|
|
self.current_pixmap: Optional[QPixmap] = None
|
|
|
|
self._setup_ui()
|
|
self._load_screenshots()
|
|
self._apply_dark_theme()
|
|
|
|
def _setup_ui(self):
|
|
"""Setup the dialog UI."""
|
|
layout = QVBoxLayout(self)
|
|
layout.setContentsMargins(10, 10, 10, 10)
|
|
layout.setSpacing(10)
|
|
|
|
# Top controls
|
|
controls_layout = QHBoxLayout()
|
|
|
|
# Filter by type
|
|
controls_layout.addWidget(QLabel("Filter:"))
|
|
self.type_filter = QComboBox()
|
|
self.type_filter.addItem("📷 All Screenshots", "all")
|
|
self.type_filter.addItem("🌟 Globals", "global")
|
|
self.type_filter.addItem("🏆 HoFs", "hof")
|
|
self.type_filter.currentIndexChanged.connect(self._on_filter_changed)
|
|
controls_layout.addWidget(self.type_filter)
|
|
|
|
controls_layout.addSpacing(20)
|
|
|
|
# Filter by session
|
|
controls_layout.addWidget(QLabel("Session:"))
|
|
self.session_filter = QComboBox()
|
|
self.session_filter.addItem("All Sessions", None)
|
|
self.session_filter.currentIndexChanged.connect(self._on_filter_changed)
|
|
controls_layout.addWidget(self.session_filter)
|
|
|
|
controls_layout.addStretch()
|
|
|
|
# Stats label
|
|
self.stats_label = QLabel("0 screenshots")
|
|
controls_layout.addWidget(self.stats_label)
|
|
|
|
controls_layout.addSpacing(20)
|
|
|
|
# Refresh button
|
|
self.refresh_btn = QPushButton("🔄 Refresh")
|
|
self.refresh_btn.clicked.connect(self._load_screenshots)
|
|
controls_layout.addWidget(self.refresh_btn)
|
|
|
|
# Open folder button
|
|
self.open_folder_btn = QPushButton("📁 Open Folder")
|
|
self.open_folder_btn.clicked.connect(self._open_screenshots_folder)
|
|
controls_layout.addWidget(self.open_folder_btn)
|
|
|
|
layout.addLayout(controls_layout)
|
|
|
|
# Main splitter
|
|
splitter = QSplitter(Qt.Orientation.Horizontal)
|
|
layout.addWidget(splitter)
|
|
|
|
# Left side - Thumbnail list
|
|
left_panel = QWidget()
|
|
left_layout = QVBoxLayout(left_panel)
|
|
left_layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
# Screenshots list
|
|
self.screenshots_list = QListWidget()
|
|
self.screenshots_list.setIconSize(QSize(120, 90))
|
|
self.screenshots_list.setViewMode(QListWidget.ViewMode.IconMode)
|
|
self.screenshots_list.setResizeMode(QListWidget.ResizeMode.Adjust)
|
|
self.screenshots_list.setSpacing(10)
|
|
self.screenshots_list.setWrapping(True)
|
|
self.screenshots_list.setMinimumWidth(400)
|
|
self.screenshots_list.itemClicked.connect(self._on_screenshot_selected)
|
|
self.screenshots_list.itemDoubleClicked.connect(self._on_screenshot_double_clicked)
|
|
|
|
left_layout.addWidget(self.screenshots_list)
|
|
|
|
splitter.addWidget(left_panel)
|
|
|
|
# Right side - Preview and details
|
|
right_panel = QWidget()
|
|
right_layout = QVBoxLayout(right_panel)
|
|
right_layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
# Image preview area
|
|
self.preview_group = QGroupBox("Preview")
|
|
preview_layout = QVBoxLayout(self.preview_group)
|
|
|
|
# Scroll area for image
|
|
self.scroll_area = QScrollArea()
|
|
self.scroll_area.setWidgetResizable(True)
|
|
self.scroll_area.setStyleSheet("background-color: #151515; border: none;")
|
|
|
|
self.image_label = ImageLabel()
|
|
self.image_label.setText("Select a screenshot to preview")
|
|
self.image_label.clicked.connect(self._open_external_viewer)
|
|
self.scroll_area.setWidget(self.image_label)
|
|
|
|
preview_layout.addWidget(self.scroll_area)
|
|
right_layout.addWidget(self.preview_group, stretch=1)
|
|
|
|
# Details panel
|
|
self.details_group = QGroupBox("Details")
|
|
details_layout = QFormLayout(self.details_group)
|
|
|
|
self.detail_id = QLabel("-")
|
|
details_layout.addRow("ID:", self.detail_id)
|
|
|
|
self.detail_timestamp = QLabel("-")
|
|
details_layout.addRow("Captured:", self.detail_timestamp)
|
|
|
|
self.detail_event_type = QLabel("-")
|
|
details_layout.addRow("Event Type:", self.detail_event_type)
|
|
|
|
self.detail_value = QLabel("-")
|
|
details_layout.addRow("Value:", self.detail_value)
|
|
|
|
self.detail_mob = QLabel("-")
|
|
details_layout.addRow("Mob:", self.detail_mob)
|
|
|
|
self.detail_session = QLabel("-")
|
|
details_layout.addRow("Session:", self.detail_session)
|
|
|
|
self.detail_file_path = QLabel("-")
|
|
self.detail_file_path.setWordWrap(True)
|
|
self.detail_file_path.setStyleSheet("font-size: 9px; color: #888;")
|
|
details_layout.addRow("File:", self.detail_file_path)
|
|
|
|
self.detail_file_size = QLabel("-")
|
|
details_layout.addRow("Size:", self.detail_file_size)
|
|
|
|
self.detail_dimensions = QLabel("-")
|
|
details_layout.addRow("Dimensions:", self.detail_dimensions)
|
|
|
|
right_layout.addWidget(self.details_group)
|
|
|
|
# Action buttons
|
|
actions_layout = QHBoxLayout()
|
|
|
|
self.open_external_btn = QPushButton("🔍 Open External")
|
|
self.open_external_btn.clicked.connect(self._open_external_viewer)
|
|
self.open_external_btn.setEnabled(False)
|
|
actions_layout.addWidget(self.open_external_btn)
|
|
|
|
self.save_as_btn = QPushButton("💾 Save As...")
|
|
self.save_as_btn.clicked.connect(self._save_as)
|
|
self.save_as_btn.setEnabled(False)
|
|
actions_layout.addWidget(self.save_as_btn)
|
|
|
|
actions_layout.addStretch()
|
|
|
|
self.delete_btn = QPushButton("🗑️ Delete")
|
|
self.delete_btn.clicked.connect(self._delete_screenshot)
|
|
self.delete_btn.setEnabled(False)
|
|
actions_layout.addWidget(self.delete_btn)
|
|
|
|
right_layout.addLayout(actions_layout)
|
|
|
|
splitter.addWidget(right_panel)
|
|
|
|
# Set splitter sizes
|
|
splitter.setSizes([500, 700])
|
|
|
|
# Close button
|
|
close_layout = QHBoxLayout()
|
|
close_layout.addStretch()
|
|
|
|
self.close_btn = QPushButton("Close")
|
|
self.close_btn.clicked.connect(self.accept)
|
|
close_layout.addWidget(self.close_btn)
|
|
|
|
layout.addLayout(close_layout)
|
|
|
|
def _load_screenshots(self):
|
|
"""Load screenshots from database."""
|
|
self.screenshots_list.clear()
|
|
self.screenshots_data = []
|
|
|
|
try:
|
|
# Load sessions for filter
|
|
self._load_sessions()
|
|
|
|
# Get filter values
|
|
event_filter = self.type_filter.currentData()
|
|
session_filter = self.session_filter.currentData()
|
|
|
|
# Build query - check both screenshots table and loot_events with screenshots
|
|
query = """
|
|
SELECT
|
|
s.id,
|
|
s.session_id,
|
|
s.timestamp,
|
|
s.file_path,
|
|
s.trigger_event,
|
|
s.trigger_value_ped as value,
|
|
le.creature_name as mob_name,
|
|
le.event_type as loot_event_type,
|
|
p.name as project_name,
|
|
hs.started_at as session_date
|
|
FROM screenshots s
|
|
JOIN sessions ses ON s.session_id = ses.id
|
|
JOIN projects p ON ses.project_id = p.id
|
|
LEFT JOIN hunting_sessions hs ON hs.session_id = s.session_id
|
|
LEFT JOIN loot_events le ON le.session_id = s.session_id
|
|
AND le.screenshot_path = s.file_path
|
|
WHERE 1=1
|
|
"""
|
|
params = []
|
|
|
|
# Apply event type filter
|
|
if event_filter == "global":
|
|
query += " AND (s.trigger_event LIKE '%global%' OR le.event_type = 'global')"
|
|
elif event_filter == "hof":
|
|
query += " AND (s.trigger_event LIKE '%hof%' OR s.trigger_event LIKE '%hall%' OR le.event_type = 'hof')"
|
|
|
|
# Apply session filter
|
|
if session_filter:
|
|
query += " AND s.session_id = ?"
|
|
params.append(session_filter)
|
|
|
|
query += " ORDER BY s.timestamp DESC"
|
|
|
|
cursor = self.db.execute(query, tuple(params))
|
|
rows = cursor.fetchall()
|
|
|
|
for row in rows:
|
|
screenshot_data = dict(row)
|
|
self.screenshots_data.append(screenshot_data)
|
|
|
|
# Create list item with thumbnail
|
|
item = QListWidgetItem()
|
|
item.setData(Qt.ItemDataRole.UserRole, screenshot_data['id'])
|
|
|
|
# Set text with timestamp and value
|
|
timestamp = datetime.fromisoformat(screenshot_data['timestamp'])
|
|
time_str = timestamp.strftime("%m/%d %H:%M")
|
|
|
|
value = screenshot_data['value'] or 0
|
|
event_type = screenshot_data['trigger_event'] or screenshot_data['loot_event_type'] or "Manual"
|
|
|
|
if value > 0:
|
|
item.setText(f"{time_str}\n{value:.0f} PED")
|
|
else:
|
|
item.setText(f"{time_str}\n{event_type}")
|
|
|
|
# Load thumbnail
|
|
file_path = screenshot_data['file_path']
|
|
if file_path and os.path.exists(file_path):
|
|
pixmap = QPixmap(file_path)
|
|
if not pixmap.isNull():
|
|
# Scale to thumbnail size
|
|
scaled = pixmap.scaled(120, 90, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation)
|
|
item.setIcon(scaled)
|
|
|
|
self.screenshots_list.addItem(item)
|
|
|
|
# Update stats
|
|
self.stats_label.setText(f"{len(self.screenshots_data)} screenshot(s)")
|
|
|
|
except Exception as e:
|
|
QMessageBox.warning(self, "Error", f"Failed to load screenshots: {e}")
|
|
|
|
def _load_sessions(self):
|
|
"""Load sessions for filter dropdown."""
|
|
current = self.session_filter.currentData()
|
|
|
|
# Block signals to prevent recursion
|
|
self.session_filter.blockSignals(True)
|
|
self.session_filter.clear()
|
|
self.session_filter.addItem("All Sessions", None)
|
|
|
|
try:
|
|
cursor = self.db.execute("""
|
|
SELECT DISTINCT s.id, p.name, s.started_at
|
|
FROM sessions s
|
|
JOIN projects p ON s.project_id = p.id
|
|
JOIN screenshots sc ON sc.session_id = s.id
|
|
ORDER BY s.started_at DESC
|
|
""")
|
|
|
|
for row in cursor.fetchall():
|
|
started = datetime.fromisoformat(row['started_at'])
|
|
label = f"{row['name']} - {started.strftime('%Y-%m-%d %H:%M')}"
|
|
self.session_filter.addItem(label, row['id'])
|
|
|
|
# Restore selection
|
|
if current:
|
|
idx = self.session_filter.findData(current)
|
|
if idx >= 0:
|
|
self.session_filter.setCurrentIndex(idx)
|
|
except Exception:
|
|
pass
|
|
finally:
|
|
# Re-enable signals
|
|
self.session_filter.blockSignals(False)
|
|
|
|
def _on_filter_changed(self):
|
|
"""Handle filter changes."""
|
|
self._load_screenshots()
|
|
|
|
def _on_screenshot_selected(self, item: QListWidgetItem):
|
|
"""Handle screenshot selection."""
|
|
screenshot_id = item.data(Qt.ItemDataRole.UserRole)
|
|
self.current_screenshot_id = screenshot_id
|
|
self._load_screenshot_details(screenshot_id)
|
|
|
|
def _on_screenshot_double_clicked(self, item: QListWidgetItem):
|
|
"""Handle double-click on screenshot."""
|
|
self._open_external_viewer()
|
|
|
|
def _load_screenshot_details(self, screenshot_id: int):
|
|
"""Load and display screenshot details."""
|
|
try:
|
|
screenshot = next((s for s in self.screenshots_data if s['id'] == screenshot_id), None)
|
|
if not screenshot:
|
|
return
|
|
|
|
file_path = screenshot['file_path']
|
|
|
|
# Update details
|
|
self.detail_id.setText(str(screenshot['id']))
|
|
|
|
timestamp = datetime.fromisoformat(screenshot['timestamp'])
|
|
self.detail_timestamp.setText(timestamp.strftime("%Y-%m-%d %H:%M:%S"))
|
|
|
|
event_type = screenshot['trigger_event'] or screenshot['loot_event_type'] or "Manual"
|
|
self.detail_event_type.setText(event_type.capitalize())
|
|
|
|
value = screenshot['value'] or 0
|
|
if value > 0:
|
|
self.detail_value.setText(f"{value:.2f} PED")
|
|
self.detail_value.setStyleSheet("color: #4caf50; font-weight: bold;")
|
|
else:
|
|
self.detail_value.setText("-")
|
|
self.detail_value.setStyleSheet("")
|
|
|
|
mob = screenshot['mob_name'] or "Unknown"
|
|
self.detail_mob.setText(mob)
|
|
|
|
session_info = f"{screenshot['project_name'] or 'Unknown'}"
|
|
if screenshot['session_date']:
|
|
session_date = datetime.fromisoformat(screenshot['session_date'])
|
|
session_info += f" ({session_date.strftime('%Y-%m-%d')})"
|
|
self.detail_session.setText(session_info)
|
|
|
|
self.detail_file_path.setText(file_path or "-")
|
|
|
|
# Load and display image
|
|
if file_path and os.path.exists(file_path):
|
|
self._display_image(file_path)
|
|
|
|
# Get file info
|
|
file_size = os.path.getsize(file_path)
|
|
self.detail_file_size.setText(self._format_file_size(file_size))
|
|
|
|
# Enable buttons
|
|
self.open_external_btn.setEnabled(True)
|
|
self.save_as_btn.setEnabled(True)
|
|
self.delete_btn.setEnabled(True)
|
|
else:
|
|
self.image_label.setText(f"File not found:\n{file_path}")
|
|
self.image_label.setPixmap(QPixmap())
|
|
self.current_pixmap = None
|
|
self.detail_file_size.setText("-")
|
|
self.detail_dimensions.setText("-")
|
|
|
|
# Disable buttons
|
|
self.open_external_btn.setEnabled(False)
|
|
self.save_as_btn.setEnabled(False)
|
|
self.delete_btn.setEnabled(True) # Still allow delete if DB record exists
|
|
|
|
except Exception as e:
|
|
QMessageBox.warning(self, "Error", f"Failed to load screenshot details: {e}")
|
|
|
|
def _display_image(self, file_path: str):
|
|
"""Load and display the image."""
|
|
pixmap = QPixmap(file_path)
|
|
|
|
if pixmap.isNull():
|
|
self.image_label.setText("Failed to load image")
|
|
self.current_pixmap = None
|
|
return
|
|
|
|
self.current_pixmap = pixmap
|
|
|
|
# Update dimensions
|
|
self.detail_dimensions.setText(f"{pixmap.width()} x {pixmap.height()}")
|
|
|
|
# Scale to fit while maintaining aspect ratio
|
|
scroll_size = self.scroll_area.size()
|
|
scaled = pixmap.scaled(
|
|
scroll_size.width() - 20,
|
|
scroll_size.height() - 20,
|
|
Qt.AspectRatioMode.KeepAspectRatio,
|
|
Qt.TransformationMode.SmoothTransformation
|
|
)
|
|
|
|
self.image_label.setPixmap(scaled)
|
|
self.image_label.setText("")
|
|
|
|
def _format_file_size(self, size_bytes: int) -> str:
|
|
"""Format file size to human readable."""
|
|
if size_bytes < 1024:
|
|
return f"{size_bytes} B"
|
|
elif size_bytes < 1024 * 1024:
|
|
return f"{size_bytes / 1024:.1f} KB"
|
|
else:
|
|
return f"{size_bytes / (1024 * 1024):.1f} MB"
|
|
|
|
def _open_external_viewer(self):
|
|
"""Open the screenshot in external viewer."""
|
|
if not self.current_screenshot_id:
|
|
return
|
|
|
|
screenshot = next(
|
|
(s for s in self.screenshots_data if s['id'] == self.current_screenshot_id),
|
|
None
|
|
)
|
|
|
|
if screenshot and screenshot['file_path'] and os.path.exists(screenshot['file_path']):
|
|
import subprocess
|
|
import platform
|
|
|
|
file_path = screenshot['file_path']
|
|
|
|
try:
|
|
if platform.system() == 'Windows':
|
|
os.startfile(file_path)
|
|
elif platform.system() == 'Darwin': # macOS
|
|
subprocess.run(['open', file_path])
|
|
else: # Linux
|
|
subprocess.run(['xdg-open', file_path])
|
|
except Exception as e:
|
|
QMessageBox.warning(self, "Error", f"Failed to open image: {e}")
|
|
|
|
def _save_as(self):
|
|
"""Save the screenshot to a new location."""
|
|
if not self.current_screenshot_id:
|
|
return
|
|
|
|
screenshot = next(
|
|
(s for s in self.screenshots_data if s['id'] == self.current_screenshot_id),
|
|
None
|
|
)
|
|
|
|
if not screenshot or not screenshot['file_path']:
|
|
return
|
|
|
|
source_path = Path(screenshot['file_path'])
|
|
|
|
file_path, _ = QFileDialog.getSaveFileName(
|
|
self,
|
|
"Save Screenshot",
|
|
f"screenshot_{screenshot['id']}.png",
|
|
"PNG Images (*.png);;JPEG Images (*.jpg *.jpeg);;All Files (*)"
|
|
)
|
|
|
|
if not file_path:
|
|
return
|
|
|
|
try:
|
|
import shutil
|
|
shutil.copy2(source_path, file_path)
|
|
QMessageBox.information(self, "Success", f"Screenshot saved to {file_path}")
|
|
except Exception as e:
|
|
QMessageBox.critical(self, "Error", f"Failed to save: {e}")
|
|
|
|
def _delete_screenshot(self):
|
|
"""Delete the selected screenshot."""
|
|
if not self.current_screenshot_id:
|
|
return
|
|
|
|
reply = QMessageBox.question(
|
|
self, "Confirm Delete",
|
|
"Are you sure you want to delete this screenshot?\n\n"
|
|
"This will permanently delete the file and its metadata.",
|
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
|
QMessageBox.StandardButton.No
|
|
)
|
|
|
|
if reply != QMessageBox.StandardButton.Yes:
|
|
return
|
|
|
|
try:
|
|
screenshot = next(
|
|
(s for s in self.screenshots_data if s['id'] == self.current_screenshot_id),
|
|
None
|
|
)
|
|
|
|
# Delete file if exists
|
|
if screenshot and screenshot['file_path'] and os.path.exists(screenshot['file_path']):
|
|
os.remove(screenshot['file_path'])
|
|
|
|
# Delete from database
|
|
self.db.execute(
|
|
"DELETE FROM screenshots WHERE id = ?",
|
|
(self.current_screenshot_id,)
|
|
)
|
|
self.db.commit()
|
|
|
|
# Emit signal
|
|
self.screenshot_deleted.emit(self.current_screenshot_id)
|
|
|
|
# Reload
|
|
self._load_screenshots()
|
|
self.current_screenshot_id = None
|
|
|
|
# Clear details
|
|
self.image_label.setText("Select a screenshot to preview")
|
|
self.image_label.setPixmap(QPixmap())
|
|
self.current_pixmap = None
|
|
|
|
self.detail_id.setText("-")
|
|
self.detail_timestamp.setText("-")
|
|
self.detail_event_type.setText("-")
|
|
self.detail_value.setText("-")
|
|
self.detail_value.setStyleSheet("")
|
|
self.detail_mob.setText("-")
|
|
self.detail_session.setText("-")
|
|
self.detail_file_path.setText("-")
|
|
self.detail_file_size.setText("-")
|
|
self.detail_dimensions.setText("-")
|
|
|
|
self.open_external_btn.setEnabled(False)
|
|
self.save_as_btn.setEnabled(False)
|
|
self.delete_btn.setEnabled(False)
|
|
|
|
except Exception as e:
|
|
QMessageBox.critical(self, "Error", f"Failed to delete screenshot: {e}")
|
|
|
|
def _open_screenshots_folder(self):
|
|
"""Open the screenshots folder in file manager."""
|
|
import subprocess
|
|
import platform
|
|
|
|
folder_path = str(self.screenshots_dir)
|
|
|
|
try:
|
|
if platform.system() == 'Windows':
|
|
os.startfile(folder_path)
|
|
elif platform.system() == 'Darwin': # macOS
|
|
subprocess.run(['open', folder_path])
|
|
else: # Linux
|
|
subprocess.run(['xdg-open', folder_path])
|
|
except Exception as e:
|
|
QMessageBox.warning(self, "Error", f"Failed to open folder: {e}")
|
|
|
|
def resizeEvent(self, event):
|
|
"""Handle resize to rescale image."""
|
|
super().resizeEvent(event)
|
|
if self.current_pixmap:
|
|
scroll_size = self.scroll_area.size()
|
|
scaled = self.current_pixmap.scaled(
|
|
scroll_size.width() - 20,
|
|
scroll_size.height() - 20,
|
|
Qt.AspectRatioMode.KeepAspectRatio,
|
|
Qt.TransformationMode.SmoothTransformation
|
|
)
|
|
self.image_label.setPixmap(scaled)
|
|
|
|
def _apply_dark_theme(self):
|
|
"""Apply dark theme styling."""
|
|
dark_stylesheet = """
|
|
QDialog {
|
|
background-color: #1e1e1e;
|
|
}
|
|
|
|
QWidget {
|
|
background-color: #1e1e1e;
|
|
color: #e0e0e0;
|
|
font-family: 'Segoe UI', Arial, sans-serif;
|
|
font-size: 10pt;
|
|
}
|
|
|
|
QGroupBox {
|
|
font-weight: bold;
|
|
border: 1px solid #444;
|
|
border-radius: 6px;
|
|
margin-top: 10px;
|
|
padding-top: 10px;
|
|
padding: 10px;
|
|
}
|
|
|
|
QGroupBox::title {
|
|
subcontrol-origin: margin;
|
|
left: 10px;
|
|
padding: 0 5px;
|
|
color: #888;
|
|
}
|
|
|
|
QPushButton {
|
|
background-color: #2d2d2d;
|
|
border: 1px solid #444;
|
|
border-radius: 4px;
|
|
padding: 8px 16px;
|
|
color: #e0e0e0;
|
|
}
|
|
|
|
QPushButton:hover {
|
|
background-color: #3d3d3d;
|
|
border-color: #555;
|
|
}
|
|
|
|
QPushButton:pressed {
|
|
background-color: #4d4d4d;
|
|
}
|
|
|
|
QPushButton:disabled {
|
|
background-color: #252525;
|
|
color: #666;
|
|
border-color: #333;
|
|
}
|
|
|
|
QListWidget {
|
|
background-color: #252525;
|
|
border: 1px solid #444;
|
|
border-radius: 4px;
|
|
outline: none;
|
|
padding: 10px;
|
|
}
|
|
|
|
QListWidget::item {
|
|
padding: 5px;
|
|
border-radius: 4px;
|
|
margin: 2px;
|
|
background-color: #2d2d2d;
|
|
}
|
|
|
|
QListWidget::item:selected {
|
|
background-color: #0d47a1;
|
|
color: white;
|
|
}
|
|
|
|
QListWidget::item:hover:!selected {
|
|
background-color: #3d3d3d;
|
|
}
|
|
|
|
QComboBox {
|
|
background-color: #252525;
|
|
border: 1px solid #444;
|
|
border-radius: 4px;
|
|
padding: 6px;
|
|
color: #e0e0e0;
|
|
min-width: 150px;
|
|
}
|
|
|
|
QComboBox:focus {
|
|
border-color: #0d47a1;
|
|
}
|
|
|
|
QComboBox::drop-down {
|
|
border: none;
|
|
padding-right: 10px;
|
|
}
|
|
|
|
QScrollArea {
|
|
border: none;
|
|
}
|
|
|
|
QLabel {
|
|
color: #e0e0e0;
|
|
}
|
|
|
|
QFormLayout QLabel {
|
|
color: #888;
|
|
}
|
|
|
|
QSplitter::handle {
|
|
background-color: #444;
|
|
}
|
|
"""
|
|
self.setStyleSheet(dark_stylesheet)
|
|
|
|
|
|
class ScreenshotCapture:
|
|
"""
|
|
Utility class for capturing screenshots.
|
|
|
|
Handles:
|
|
- Screen capture
|
|
- Saving to file with metadata
|
|
- Database recording
|
|
"""
|
|
|
|
def __init__(self, db: Optional[DatabaseManager] = None):
|
|
self.db = db or DatabaseManager()
|
|
# Default to Documents/Entropia Universe/Screenshots
|
|
self.screenshots_dir = Path.home() / "Documents" / "Entropia Universe" / "Screenshots"
|
|
self.screenshots_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
def capture(self, session_id: int, trigger_event: str = "manual",
|
|
value_ped: float = 0.0, mob_name: str = "") -> Optional[str]:
|
|
"""
|
|
Capture a screenshot and save it.
|
|
|
|
Args:
|
|
session_id: The current session ID
|
|
trigger_event: What triggered the screenshot (global, hof, manual)
|
|
value_ped: Value of the event in PED
|
|
mob_name: Name of the mob/creature
|
|
|
|
Returns:
|
|
Path to the saved screenshot, or None if failed
|
|
"""
|
|
if not PIL_AVAILABLE:
|
|
print("Screenshot capture requires PIL (Pillow). Install with: pip install Pillow")
|
|
return None
|
|
|
|
try:
|
|
# Generate filename with timestamp
|
|
timestamp = datetime.now()
|
|
filename = f"{timestamp.strftime('%Y%m%d_%H%M%S')}_{trigger_event}"
|
|
if value_ped > 0:
|
|
filename += f"_{value_ped:.0f}PED"
|
|
filename += ".png"
|
|
|
|
file_path = self.screenshots_dir / filename
|
|
|
|
# Capture screenshot using PIL
|
|
screenshot = ImageGrab.grab()
|
|
screenshot.save(file_path, "PNG")
|
|
|
|
# Save metadata to database
|
|
cursor = self.db.execute("""
|
|
INSERT INTO screenshots
|
|
(session_id, timestamp, file_path, trigger_event, trigger_value_ped)
|
|
VALUES (?, ?, ?, ?, ?)
|
|
""", (
|
|
session_id,
|
|
timestamp.isoformat(),
|
|
str(file_path),
|
|
trigger_event,
|
|
value_ped
|
|
))
|
|
|
|
# Also update loot_events if there's a matching recent event
|
|
self.db.execute("""
|
|
UPDATE loot_events
|
|
SET screenshot_path = ?
|
|
WHERE session_id = ?
|
|
AND screenshot_path IS NULL
|
|
AND event_type IN ('global', 'hof')
|
|
ORDER BY timestamp DESC
|
|
LIMIT 1
|
|
""", (str(file_path), session_id))
|
|
|
|
self.db.commit()
|
|
|
|
return str(file_path)
|
|
|
|
except Exception as e:
|
|
print(f"Screenshot capture failed: {e}")
|
|
return None
|
|
|
|
def capture_global(self, session_id: int, value_ped: float, mob_name: str = "") -> Optional[str]:
|
|
"""Capture screenshot for a global event."""
|
|
return self.capture(session_id, "global", value_ped, mob_name)
|
|
|
|
def capture_hof(self, session_id: int, value_ped: float, mob_name: str = "") -> Optional[str]:
|
|
"""Capture screenshot for a HoF event."""
|
|
return self.capture(session_id, "hof", value_ped, mob_name)
|
|
|
|
def capture_manual(self, session_id: int) -> Optional[str]:
|
|
"""Capture manual screenshot."""
|
|
return self.capture(session_id, "manual", 0.0, "")
|