#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Screenshot management panel - manual screenshot, view history, open directory """ import os import subprocess import sys from datetime import datetime from PyQt6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QListWidget, QListWidgetItem, QComboBox, QGroupBox, QMessageBox, QFileDialog, QScrollArea, QFrame, QSizePolicy ) from PyQt6.QtCore import Qt, pyqtSignal, QSize from PyQt6.QtGui import QPixmap, QIcon from config import get_config, SCREENSHOTS_DIR from utils.crypto import decrypt_password, is_encrypted from .constants import ( PAGE_PADDING, SECTION_SPACING, GROUP_PADDING_TOP, GROUP_PADDING_SIDE, GROUP_PADDING_BOTTOM, GROUP_SPACING, FORM_LABEL_WIDTH, FORM_H_SPACING, INPUT_HEIGHT, INPUT_MIN_WIDTH, BUTTON_HEIGHT, BUTTON_HEIGHT_NORMAL, BUTTON_MIN_WIDTH, BUTTON_SPACING, LIST_MIN_HEIGHT, ICON_SIZE_LARGE, get_title_style ) class ScreenshotWidget(QWidget): """Screenshot management panel""" log_signal = pyqtSignal(str) def __init__(self, parent=None): super().__init__(parent) self._worker = None self._setup_ui() self._refresh_screenshots() def _setup_ui(self): main_layout = QVBoxLayout(self) main_layout.setContentsMargins(0, 0, 0, 0) main_layout.setSpacing(0) # Scroll area scroll = QScrollArea() scroll.setWidgetResizable(True) scroll.setFrameShape(QFrame.Shape.NoFrame) scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) content = QWidget() layout = QVBoxLayout(content) layout.setContentsMargins(PAGE_PADDING, PAGE_PADDING, PAGE_PADDING, PAGE_PADDING) layout.setSpacing(SECTION_SPACING) # Title title = QLabel("📸 截图管理") title.setStyleSheet(get_title_style()) layout.addWidget(title) # ==================== Manual screenshot ==================== manual_group = QGroupBox("手动截图") manual_layout = QVBoxLayout(manual_group) manual_layout.setContentsMargins(GROUP_PADDING_SIDE, GROUP_PADDING_TOP, GROUP_PADDING_SIDE, GROUP_PADDING_BOTTOM) manual_layout.setSpacing(GROUP_SPACING) # Account row account_layout = QHBoxLayout() account_layout.setSpacing(FORM_H_SPACING) account_label = QLabel("选择账号:") account_label.setFixedWidth(FORM_LABEL_WIDTH) account_layout.addWidget(account_label) self.account_combo = QComboBox() self.account_combo.setMinimumWidth(INPUT_MIN_WIDTH + 100) self.account_combo.setMinimumHeight(INPUT_HEIGHT) account_layout.addWidget(self.account_combo) refresh_account_btn = QPushButton("🔄 刷新") refresh_account_btn.setMinimumHeight(BUTTON_HEIGHT_NORMAL) refresh_account_btn.clicked.connect(self._refresh_accounts) account_layout.addWidget(refresh_account_btn) account_layout.addStretch() manual_layout.addLayout(account_layout) # Browse type row type_layout = QHBoxLayout() type_layout.setSpacing(FORM_H_SPACING) type_label = QLabel("浏览类型:") type_label.setFixedWidth(FORM_LABEL_WIDTH) type_layout.addWidget(type_label) self.browse_type_combo = QComboBox() self.browse_type_combo.addItems(["应读", "注册前未读"]) self.browse_type_combo.setMinimumWidth(INPUT_MIN_WIDTH) self.browse_type_combo.setMinimumHeight(INPUT_HEIGHT) type_layout.addWidget(self.browse_type_combo) type_layout.addStretch() manual_layout.addLayout(type_layout) # Screenshot button btn_layout = QHBoxLayout() btn_layout.setSpacing(BUTTON_SPACING) self.screenshot_btn = QPushButton("📷 执行截图") self.screenshot_btn.setObjectName("primary") self.screenshot_btn.setMinimumWidth(BUTTON_MIN_WIDTH) self.screenshot_btn.setMinimumHeight(BUTTON_HEIGHT) self.screenshot_btn.clicked.connect(self._take_screenshot) btn_layout.addWidget(self.screenshot_btn) btn_layout.addStretch() manual_layout.addLayout(btn_layout) layout.addWidget(manual_group) # ==================== Screenshot history ==================== history_group = QGroupBox("截图历史") history_group.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) history_layout = QVBoxLayout(history_group) history_layout.setContentsMargins(GROUP_PADDING_SIDE, GROUP_PADDING_TOP, GROUP_PADDING_SIDE, GROUP_PADDING_BOTTOM) history_layout.setSpacing(GROUP_SPACING) # Toolbar toolbar = QHBoxLayout() toolbar.setSpacing(BUTTON_SPACING) open_dir_btn = QPushButton("📂 打开目录") open_dir_btn.setMinimumHeight(BUTTON_HEIGHT_NORMAL) open_dir_btn.clicked.connect(self._open_screenshot_dir) toolbar.addWidget(open_dir_btn) toolbar.addStretch() refresh_btn = QPushButton("🔄 刷新列表") refresh_btn.setMinimumHeight(BUTTON_HEIGHT_NORMAL) refresh_btn.clicked.connect(self._refresh_screenshots) toolbar.addWidget(refresh_btn) history_layout.addLayout(toolbar) # Screenshot list - 图标模式,一行显示多个 self.screenshot_list = QListWidget() self.screenshot_list.setViewMode(QListWidget.ViewMode.IconMode) # 图标模式 self.screenshot_list.setIconSize(QSize(150, 100)) # 缩略图大小 self.screenshot_list.setGridSize(QSize(170, 130)) # 网格大小(含文字) self.screenshot_list.setResizeMode(QListWidget.ResizeMode.Adjust) # 自动调整 self.screenshot_list.setWrapping(True) # 自动换行 self.screenshot_list.setSpacing(8) self.screenshot_list.setMinimumHeight(200) self.screenshot_list.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) self.screenshot_list.itemDoubleClicked.connect(self._preview_screenshot) history_layout.addWidget(self.screenshot_list, 1) # Action buttons action_layout = QHBoxLayout() action_layout.setSpacing(BUTTON_SPACING) self.delete_all_btn = QPushButton("🗑 删除全部") self.delete_all_btn.setObjectName("danger") self.delete_all_btn.setMinimumHeight(BUTTON_HEIGHT_NORMAL) self.delete_all_btn.clicked.connect(self._delete_all) action_layout.addWidget(self.delete_all_btn) action_layout.addStretch() self.preview_btn = QPushButton("👁 预览") self.preview_btn.setMinimumHeight(BUTTON_HEIGHT_NORMAL) self.preview_btn.clicked.connect(self._preview_selected) action_layout.addWidget(self.preview_btn) self.delete_btn = QPushButton("🗑 删除选中") self.delete_btn.setMinimumHeight(BUTTON_HEIGHT_NORMAL) self.delete_btn.clicked.connect(self._delete_selected) action_layout.addWidget(self.delete_btn) history_layout.addLayout(action_layout) layout.addWidget(history_group, 1) scroll.setWidget(content) main_layout.addWidget(scroll) # Load accounts self._refresh_accounts() def _refresh_accounts(self): """Refresh account list""" self.account_combo.clear() config = get_config() for account in config.accounts: if account.enabled: display_name = account.remark if account.remark else account.username self.account_combo.addItem(f"{display_name} ({account.username})", account) def _refresh_screenshots(self): """Refresh screenshot list""" self.screenshot_list.clear() if not SCREENSHOTS_DIR.exists(): return files = [] for f in SCREENSHOTS_DIR.iterdir(): if f.suffix.lower() in (".jpg", ".jpeg", ".png"): files.append(f) # Sort by modification time, newest first files.sort(key=lambda x: x.stat().st_mtime, reverse=True) for f in files[:50]: # Only show last 50 item = QListWidgetItem() # 文件名太长时截断显示 name = f.stem # 不含扩展名 if len(name) > 15: name = name[:12] + "..." item.setText(name) item.setData(Qt.ItemDataRole.UserRole, str(f)) item.setToolTip(f.name) # 完整文件名显示在提示中 # Try to load thumbnail try: pixmap = QPixmap(str(f)) if not pixmap.isNull(): # 缩放到合适大小显示缩略图 scaled = pixmap.scaled( 150, 100, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation ) item.setIcon(QIcon(scaled)) except Exception as e: print(f"加载缩略图失败: {f.name}, {e}") self.screenshot_list.addItem(item) def _take_screenshot(self): """Execute screenshot""" if self.account_combo.currentIndex() < 0: QMessageBox.information(self, "提示", "请先选择一个账号") return account = self.account_combo.currentData() if not account: return browse_type = self.browse_type_combo.currentText() password = decrypt_password(account.password) if is_encrypted(account.password) else account.password self.screenshot_btn.setEnabled(False) self.screenshot_btn.setText("截图中...") self.log_signal.emit(f"开始截图: {account.username}") from utils.worker import Worker def do_screenshot(_signals=None, _should_stop=None): from core.screenshot import take_screenshot from config import get_config as _get_config cfg = _get_config() proxy_config = None if cfg.proxy.enabled and cfg.proxy.server: proxy_config = {"server": cfg.proxy.server} result = take_screenshot( account.username, password, browse_type, remark=account.remark, log_callback=lambda msg: _signals.log.emit(msg), proxy_config=proxy_config ) return result def on_result(result): self.screenshot_btn.setEnabled(True) self.screenshot_btn.setText("📷 执行截图") if result and result.success: self.log_signal.emit(f"[OK] 截图成功: {result.filename}") self._refresh_screenshots() else: error = result.error_message if result else "未知错误" self.log_signal.emit(f"[FAIL] 截图失败: {error}") def on_error(error): self.screenshot_btn.setEnabled(True) self.screenshot_btn.setText("📷 执行截图") self.log_signal.emit(f"截图出错: {error}") self._worker = Worker(do_screenshot) self._worker.signals.log.connect(self.log_signal.emit) self._worker.signals.result.connect(on_result) self._worker.signals.error.connect(on_error) self._worker.start() def _preview_screenshot(self, item): """Preview screenshot""" filepath = item.data(Qt.ItemDataRole.UserRole) if filepath and os.path.exists(filepath): if sys.platform == "win32": os.startfile(filepath) elif sys.platform == "darwin": subprocess.run(["open", filepath]) else: subprocess.run(["xdg-open", filepath]) def _preview_selected(self): """Preview selected screenshot""" current = self.screenshot_list.currentItem() if current: self._preview_screenshot(current) else: QMessageBox.information(self, "提示", "请先选择一个截图") def _delete_selected(self): """Delete selected screenshot""" current = self.screenshot_list.currentItem() if not current: QMessageBox.information(self, "提示", "请先选择一个截图") return filepath = current.data(Qt.ItemDataRole.UserRole) filename = current.text() reply = QMessageBox.question( self, "确认删除", f"确定要删除截图 {filename} 吗?", QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No ) if reply == QMessageBox.StandardButton.Yes: try: if os.path.exists(filepath): os.remove(filepath) self._refresh_screenshots() self.log_signal.emit(f"已删除截图: {filename}") except Exception as e: QMessageBox.warning(self, "错误", f"删除失败: {e}") def _delete_all(self): """Delete all screenshots""" count = self.screenshot_list.count() if count == 0: QMessageBox.information(self, "提示", "没有截图可删除") return reply = QMessageBox.warning( self, "⚠️ 确认删除全部", f"确定要删除全部 {count} 张截图吗?\n\n此操作不可恢复!", QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, QMessageBox.StandardButton.No ) if reply == QMessageBox.StandardButton.Yes: deleted = 0 failed = 0 for i in range(count): item = self.screenshot_list.item(i) filepath = item.data(Qt.ItemDataRole.UserRole) try: if os.path.exists(filepath): os.remove(filepath) deleted += 1 except Exception: failed += 1 self._refresh_screenshots() msg = f"已删除 {deleted} 张截图" if failed > 0: msg += f",{failed} 张删除失败" self.log_signal.emit(msg) def _open_screenshot_dir(self): """Open screenshot directory""" SCREENSHOTS_DIR.mkdir(exist_ok=True) if sys.platform == "win32": os.startfile(str(SCREENSHOTS_DIR)) elif sys.platform == "darwin": subprocess.run(["open", str(SCREENSHOTS_DIR)]) else: subprocess.run(["xdg-open", str(SCREENSHOTS_DIR)])