10 Commits
1.1.3 ... 1.2.0

Author SHA1 Message Date
hyb-oyqq
331f7a25d2 refactor(ui): 重构 UI 相关代码并移除冗余资源
- 从 ui_manager.py 和 install.ui 中移除了使用 base64 图片数据的方式
- 采用直接加载图片文件的方法,提高了代码的可读性和维护性
- 删除了未使用的 popup.ui 文件
- 优化了资源路径的获取方式,使用 resource_path 函数统一处理
2025-07-28 15:22:31 +08:00
hyb-oyqq
642b2ec17f feat(main_window): 添加批量卸载游戏补丁功能
- 在卸载补丁界面添加"全部卸载"选项
- 实现批量卸载所有游戏补丁的功能
- 优化单个游戏卸载流程,统一卸载成功和失败的返回值
- 调整卸载消息提示,适应批量卸载和单个卸载的不同场景
2025-07-28 13:09:43 +08:00
hyb-oyqq
41aab89669 feat(core): 优化游戏目录识别和补丁卸载流程
- 改进游戏目录识别算法,支持大小写不敏感和特殊字符处理
- 增加递归搜索可执行文件的功能,提高识别准确率
- 优化补丁卸载流程,支持更灵活的文件路径和名称
- 增加调试模式下的日志输出,便于问题排查
- 重构部分代码结构,提高可维护性和可扩展性
2025-07-28 11:54:52 +08:00
hyb-oyqq
f6a57215c2 refactor(source): 优化窗口大小和背景图显示
- 设置窗口最小和最大尺寸
- 优化背景图加载和显示,使用setScaledContents简化处理
- 调整标题栏和菜单区域的大小和位置
- 重构部分UI代码,提高可读性和维护性
2025-07-25 17:21:30 +08:00
hyb-oyqq
38549e098e feat(core): 添加卸载补丁功能并优化用户界面
- 在主界面添加卸载补丁按钮,实现卸载功能
- 优化菜单区域,使用按钮替代传统菜单栏
- 更新主题颜色,调整按钮布局和样式
- 优化帮助和设置菜单,提升用户体验
2025-07-25 17:00:55 +08:00
hyb-oyqq
286270a819 feat(core): 优化窗口动画和布局
- 新增按钮点击动画效果
- 动态调整窗口大小和布局
- 实现圆角窗口和拖动功能
- 优化安装按钮状态管理
- 更新菜单动画逻辑
- 为2.0新窗口做预实现
2025-07-25 15:18:05 +08:00
hyb-oyqq
0f9c91b59a docs(README): 更新文档状态说明
- 中英文 README 文件中的文档状态说明已更新
2025-07-24 18:24:58 +08:00
hyb-oyqq
3753375bed feat(core): 重构按钮显示和动画
- 更新动画逻辑,使用新的按钮容器
- 优化按钮点击区域和视觉效果
- 添加了得意黑作为字体和样式
2025-07-24 17:43:11 +08:00
hyb-oyqq
dab2ba2dc5 feat(core): 优化安装结果显示和下载管理
- 优化 Cloudflare 加速提示信息格式
- 增加下载历史记录列表,跟踪本次安装的游戏
- 改进安装结果显示,区分不同情况
- 优化哈希检查提示信息
2025-07-24 16:57:16 +08:00
hyb-oyqq
f86cb7aa7e refactor(core): 优化消息框显示和下载流程
- 修改 hash_pop_window 函数,增加检查类型参数
- 优化 Cloudflare 优选失败和成功消息框显示
- 调整下载前后检查的消息框内容
- 改进解压缩后的文件检查流程
2025-07-24 16:29:30 +08:00
15 changed files with 1830 additions and 557 deletions

View File

@@ -1,7 +1,7 @@
# 🍓FRAISEMOE-Addons-Installer-NEXT🍓
```
🔊 Note: This repository is still under active development, and most of the documentation is not yet available. We appreciate your understanding.
🔊 Note: This repository's documentation updates have stabilized. If there are any missing parts, please promptly raise an issue. Thank you all for your support!
The English version is not updated in real-time! Please check the Simplified Chinese version for more updates! Thank you for your support!
```

View File

@@ -1,7 +1,7 @@
# 🍓FRAISEMOE-Addons-Installer-NEXT🍓
```
🔊 注意:本库仍然努力更新中,大部分文档不可用,敬请谅解。
🔊 注意:本库文档更新已趋于稳定如有遗漏部分请及时提出issue感谢各位支持
```
<!-- PROJECT SHIELDS -->

BIN
source/IMG/BG/title_bg1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
source/IMG/BG/title_bg2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
source/IMG/BTN/Button.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -1,15 +1,24 @@
import sys
from PySide6.QtCore import (QObject, QPropertyAnimation, QParallelAnimationGroup,
QPoint, QEasingCurve, QTimer, Signal)
from PySide6.QtWidgets import QGraphicsOpacityEffect
QPoint, QEasingCurve, QTimer, Signal, QRect)
from PySide6.QtWidgets import QGraphicsOpacityEffect, QPushButton
from PySide6.QtGui import QColor
class MultiStageAnimations(QObject):
animation_finished = Signal()
def __init__(self, ui, parent=None):
super().__init__(parent)
self.ui = ui
# 获取画布尺寸
self.canvas_width = ui.centralwidget.width()
self.canvas_height = ui.centralwidget.height()
self.parent = parent # 保存父窗口引用以获取当前尺寸
# 获取画布尺寸 - 动态从父窗口获取
if parent:
self.canvas_width = parent.width()
self.canvas_height = parent.height()
else:
# 默认尺寸
self.canvas_width = 1280
self.canvas_height = 720
# 动画时序配置
self.animation_config = {
@@ -18,29 +27,141 @@ class MultiStageAnimations(QObject):
},
"mainbg": {
"delay_after": 500
},
"button_click": {
"scale_duration": 100,
"scale_min": 0.95,
"scale_max": 1.0
}
}
# 第一阶段Logo动画配置
# 第一阶段Logo动画配置根据新布局调整Y坐标
self.logo_widgets = [
{"widget": ui.vol1bg, "delay": 0, "duration": 500, "end_pos": QPoint(0, 120)},
{"widget": ui.vol2bg, "delay": 80, "duration": 500, "end_pos": QPoint(0, 180)},
{"widget": ui.vol3bg, "delay": 160, "duration": 500, "end_pos": QPoint(0, 240)},
{"widget": ui.vol4bg, "delay": 240, "duration": 500, "end_pos": QPoint(0, 300)},
{"widget": ui.afterbg, "delay": 320, "duration": 500, "end_pos": QPoint(0, 360)}
{"widget": ui.vol1bg, "delay": 0, "duration": 500, "end_pos": QPoint(0, 150)},
{"widget": ui.vol2bg, "delay": 80, "duration": 500, "end_pos": QPoint(0, 210)},
{"widget": ui.vol3bg, "delay": 160, "duration": 500, "end_pos": QPoint(0, 270)},
{"widget": ui.vol4bg, "delay": 240, "duration": 500, "end_pos": QPoint(0, 330)},
{"widget": ui.afterbg, "delay": 320, "duration": 500, "end_pos": QPoint(0, 390)}
]
# 第二阶段:菜单元素
# 第二阶段:菜单元素,位置会在开始动画时动态计算
self.menu_widgets = [
{"widget": ui.menubg, "end_pos": QPoint(710, 0), "duration": 600},
{"widget": ui.start_install_btn, "end_pos": QPoint(780, 250), "duration": 600},
{"widget": ui.exit_btn, "end_pos": QPoint(780, 340), "duration": 600}
# 移除菜单背景动画
# {"widget": ui.menubg, "end_pos": QPoint(720, 55), "duration": 600},
{"widget": ui.button_container, "end_pos": None, "duration": 600},
{"widget": ui.uninstall_container, "end_pos": None, "duration": 600}, # 添加卸载补丁按钮
{"widget": ui.exit_container, "end_pos": None, "duration": 600}
]
self.animations = []
self.timers = []
# 设置按钮点击动画
self.setup_button_click_animations()
def setup_button_click_animations(self):
"""设置按钮点击动画"""
# 为开始安装按钮添加点击动画
self.ui.start_install_btn.pressed.connect(
lambda: self.start_button_click_animation(self.ui.button_container)
)
self.ui.start_install_btn.released.connect(
lambda: self.end_button_click_animation(self.ui.button_container)
)
# 为卸载补丁按钮添加点击动画
self.ui.uninstall_btn.pressed.connect(
lambda: self.start_button_click_animation(self.ui.uninstall_container)
)
self.ui.uninstall_btn.released.connect(
lambda: self.end_button_click_animation(self.ui.uninstall_container)
)
# 为退出按钮添加点击动画
self.ui.exit_btn.pressed.connect(
lambda: self.start_button_click_animation(self.ui.exit_container)
)
self.ui.exit_btn.released.connect(
lambda: self.end_button_click_animation(self.ui.exit_container)
)
def start_button_click_animation(self, button_container):
"""开始按钮点击动画"""
# 创建缩放动画
scale_anim = QPropertyAnimation(button_container.children()[0], b"geometry") # 只对按钮背景应用动画
scale_anim.setDuration(self.animation_config["button_click"]["scale_duration"])
# 获取当前几何形状
current_geometry = button_container.children()[0].geometry()
# 计算缩放后的几何形状(保持中心点不变)
scale_factor = self.animation_config["button_click"]["scale_min"]
width_diff = current_geometry.width() * (1 - scale_factor) / 2
height_diff = current_geometry.height() * (1 - scale_factor) / 2
new_geometry = QRect(
current_geometry.x() + width_diff,
current_geometry.y() + height_diff,
current_geometry.width() * scale_factor,
current_geometry.height() * scale_factor
)
scale_anim.setEndValue(new_geometry)
scale_anim.setEasingCurve(QEasingCurve.Type.OutQuad)
# 启动动画
scale_anim.start()
self.animations.append(scale_anim)
# 对文本标签也应用同样的动画
text_anim = QPropertyAnimation(button_container.children()[1], b"geometry")
text_anim.setDuration(self.animation_config["button_click"]["scale_duration"])
text_geometry = button_container.children()[1].geometry()
new_text_geometry = QRect(
text_geometry.x() + width_diff,
text_geometry.y() + height_diff,
text_geometry.width() * scale_factor,
text_geometry.height() * scale_factor
)
text_anim.setEndValue(new_text_geometry)
text_anim.setEasingCurve(QEasingCurve.Type.OutQuad)
text_anim.start()
self.animations.append(text_anim)
def end_button_click_animation(self, button_container):
"""结束按钮点击动画,恢复正常外观"""
# 创建恢复动画 - 对背景
scale_anim = QPropertyAnimation(button_container.children()[0], b"geometry")
scale_anim.setDuration(self.animation_config["button_click"]["scale_duration"])
# 恢复到原始大小 (10,10,191,91)
original_geometry = QRect(10, 10, 191, 91)
scale_anim.setEndValue(original_geometry)
scale_anim.setEasingCurve(QEasingCurve.Type.OutElastic)
# 启动动画
scale_anim.start()
self.animations.append(scale_anim)
# 恢复文本标签
text_anim = QPropertyAnimation(button_container.children()[1], b"geometry")
text_anim.setDuration(self.animation_config["button_click"]["scale_duration"])
# 恢复文本到原始大小 (10,7,191,91)
text_anim.setEndValue(QRect(10, 7, 191, 91))
text_anim.setEasingCurve(QEasingCurve.Type.OutElastic)
text_anim.start()
self.animations.append(text_anim)
def initialize(self):
"""初始化所有组件状态"""
# 更新画布尺寸
if self.parent:
self.canvas_width = self.parent.width()
self.canvas_height = self.parent.height()
# 设置Mainbg初始状态
effect = QGraphicsOpacityEffect(self.ui.Mainbg)
effect.setOpacity(0)
@@ -125,6 +246,12 @@ class MultiStageAnimations(QObject):
self.animations.append(main_anim)
def start_menu_animations(self):
"""启动菜单动画(从下往上)"""
# 更新按钮最终位置
self._update_button_positions()
# 跟踪最后一个动画用于连接finished信号
last_anim = None
for item in self.menu_widgets:
anim_group = QParallelAnimationGroup()
@@ -144,11 +271,69 @@ class MultiStageAnimations(QObject):
anim_group.addAnimation(pos_anim)
anim_group.addAnimation(opacity_anim)
if item["widget"] == self.ui.exit_btn:
anim_group.finished.connect(self.animation_finished.emit)
# 记录最后一个按钮的动画
if item["widget"] == self.ui.exit_container:
last_anim = anim_group
anim_group.start()
self.animations.append(anim_group)
# 在最后一个动画完成时发出信号
if last_anim:
last_anim.finished.connect(self.animation_finished.emit)
def _update_button_positions(self):
"""更新按钮最终位置"""
# 根据当前窗口大小动态计算按钮位置
if self.parent:
width = self.parent.width()
height = self.parent.height()
# 计算按钮位置
right_margin = 20 # 减小右边距,使按钮更靠右
# 开始安装按钮
if hasattr(self.ui, 'button_container'):
btn_width = self.ui.button_container.width()
x_pos = width - btn_width - right_margin
y_pos = int((height - 65) * 0.28) - 10 # 与resizeEvent中保持一致
# 更新动画目标位置
for item in self.menu_widgets:
if item["widget"] == self.ui.button_container:
item["end_pos"] = QPoint(x_pos, y_pos)
# 卸载补丁按钮
if hasattr(self.ui, 'uninstall_container'):
btn_width = self.ui.uninstall_container.width()
x_pos = width - btn_width - right_margin
y_pos = int((height - 65) * 0.46) - 10 # 与resizeEvent中保持一致
# 更新动画目标位置
for item in self.menu_widgets:
if item["widget"] == self.ui.uninstall_container:
item["end_pos"] = QPoint(x_pos, y_pos)
# 退出按钮
if hasattr(self.ui, 'exit_container'):
btn_width = self.ui.exit_container.width()
x_pos = width - btn_width - right_margin
y_pos = int((height - 65) * 0.64) - 10 # 与resizeEvent中保持一致
# 更新动画目标位置
for item in self.menu_widgets:
if item["widget"] == self.ui.exit_container:
item["end_pos"] = QPoint(x_pos, y_pos)
else:
# 默认位置
for item in self.menu_widgets:
if item["widget"] == self.ui.button_container:
item["end_pos"] = QPoint(1050, 200)
elif item["widget"] == self.ui.uninstall_container:
item["end_pos"] = QPoint(1050, 310)
elif item["widget"] == self.ui.exit_container:
item["end_pos"] = QPoint(1050, 420)
def start_animations(self):
"""启动完整动画序列"""
self.clear_animations()

View File

@@ -3,8 +3,9 @@ import requests
import json
from collections import deque
from urllib.parse import urlparse
import re # Added for recursive search
from PySide6 import QtWidgets
from PySide6 import QtWidgets, QtCore
from PySide6.QtCore import Qt
from PySide6.QtGui import QIcon, QPixmap
@@ -42,10 +43,29 @@ class DownloadManager:
def get_install_paths(self):
"""获取所有游戏版本的安装路径"""
return {
game: os.path.join(self.selected_folder, info["install_path"])
for game, info in GAME_INFO.items()
}
# 使用改进的目录识别功能
game_dirs = self.main_window.identify_game_directories_improved(self.selected_folder)
install_paths = {}
debug_mode = self.is_debug_mode()
for game, info in GAME_INFO.items():
if game in game_dirs:
# 如果找到了游戏目录,使用它
game_dir = game_dirs[game]
install_path = os.path.join(game_dir, os.path.basename(info["install_path"]))
install_paths[game] = install_path
if debug_mode:
print(f"DEBUG: 使用识别到的游戏目录 {game}: {game_dir}")
print(f"DEBUG: 安装路径设置为: {install_path}")
else:
# 回退到原始路径计算方式
install_path = os.path.join(self.selected_folder, info["install_path"])
install_paths[game] = install_path
if debug_mode:
print(f"DEBUG: 未识别到游戏目录 {game}, 使用默认路径: {install_path}")
return install_paths
def is_debug_mode(self):
"""检查是否处于调试模式"""
@@ -140,10 +160,33 @@ class DownloadManager:
def download_action(self):
"""开始下载流程"""
# 禁用开始安装按钮
self.main_window.ui.start_install_btn.setEnabled(False)
self.main_window.set_start_button_enabled(False)
# 清空下载历史记录
self.main_window.download_queue_history = []
# 使用改进的目录识别功能
game_dirs = self.main_window.identify_game_directories_improved(self.selected_folder)
debug_mode = self.is_debug_mode()
if debug_mode:
print(f"DEBUG: 开始下载流程, 识别到 {len(game_dirs)} 个游戏目录")
# 检查是否找到任何游戏目录
if not game_dirs:
if debug_mode:
print("DEBUG: 未识别到任何游戏目录,设置目录未找到错误")
# 设置特定的错误类型,以便在按钮点击处理中区分处理
self.main_window.last_error_message = "directory_not_found"
QtWidgets.QMessageBox.warning(
self.main_window,
f"目录错误 - {APP_NAME}",
"\n未能识别到任何游戏目录。\n\n请确认您选择的是游戏的上级目录并且该目录中包含NEKOPARA系列游戏文件夹。\n"
)
return
# 显示哈希检查窗口
self.main_window.hash_msg_box = self.main_window.hash_manager.hash_pop_window()
self.main_window.hash_msg_box = self.main_window.hash_manager.hash_pop_window(check_type="pre")
# 执行预检查
install_paths = self.get_install_paths()
@@ -170,7 +213,7 @@ class DownloadManager:
self.main_window, f"错误 - {APP_NAME}", "\n网络状态异常或服务器故障,请重试\n"
)
# 重新启用开始安装按钮
self.main_window.ui.start_install_btn.setEnabled(True)
self.main_window.set_start_button_enabled(True)
return
# 填充下载队列
@@ -185,7 +228,7 @@ class DownloadManager:
# 询问用户是否使用Cloudflare加速
msg_box = QtWidgets.QMessageBox(self.main_window)
msg_box.setWindowTitle(f"下载优化 - {APP_NAME}")
msg_box.setText("是否愿意通过Cloudflare加速来优化下载速度\n\n这将临时修改系统的hosts文件并需要管理员权限。")
msg_box.setText("是否愿意通过Cloudflare加速来优化下载速度\n\n这将临时修改系统的hosts文件并需要管理员权限。\n如您的杀毒软件提醒有软件正在修改hosts文件请注意放行。")
# 设置Cloudflare图标
cf_icon_path = resource_path("IMG/ICO/cloudflare_logo_icon.ico")
@@ -221,26 +264,61 @@ class DownloadManager:
# 清空现有队列
self.download_queue.clear()
# 创建下载历史记录列表,用于跟踪本次安装的游戏
if not hasattr(self.main_window, 'download_queue_history'):
self.main_window.download_queue_history = []
# 获取所有识别到的游戏目录
game_dirs = self.main_window.identify_game_directories_improved(self.selected_folder)
debug_mode = self.is_debug_mode()
if debug_mode:
print(f"DEBUG: 填充下载队列, 识别到的游戏目录: {game_dirs}")
# 添加nekopara 1-4
for i in range(1, 5):
game_version = f"NEKOPARA Vol.{i}"
if not self.main_window.installed_status.get(game_version, False):
url = config.get(f"vol{i}")
if not url: continue
game_folder = os.path.join(self.selected_folder, f"NEKOPARA Vol. {i}")
# 确定游戏文件夹路径
if game_version in game_dirs:
game_folder = game_dirs[game_version]
if debug_mode:
print(f"DEBUG: 使用识别到的游戏目录 {game_version}: {game_folder}")
else:
# 回退到传统方式
game_folder = os.path.join(self.selected_folder, f"NEKOPARA Vol. {i}")
if debug_mode:
print(f"DEBUG: 使用默认游戏目录 {game_version}: {game_folder}")
_7z_path = os.path.join(PLUGIN, f"vol.{i}.7z")
plugin_path = os.path.join(PLUGIN, GAME_INFO[game_version]["plugin_path"])
self.download_queue.append((url, game_folder, game_version, _7z_path, plugin_path))
# 记录到下载历史
self.main_window.download_queue_history.append(game_version)
# 添加nekopara after
game_version = "NEKOPARA After"
if not self.main_window.installed_status.get(game_version, False):
url = config.get("after")
if url:
game_folder = os.path.join(self.selected_folder, "NEKOPARA After")
# 确定After的游戏文件夹路径
if game_version in game_dirs:
game_folder = game_dirs[game_version]
if debug_mode:
print(f"DEBUG: 使用识别到的游戏目录 {game_version}: {game_folder}")
else:
game_folder = os.path.join(self.selected_folder, "NEKOPARA After")
if debug_mode:
print(f"DEBUG: 使用默认游戏目录 {game_version}: {game_folder}")
_7z_path = os.path.join(PLUGIN, "after.7z")
plugin_path = os.path.join(PLUGIN, GAME_INFO[game_version]["plugin_path"])
self.download_queue.append((url, game_folder, game_version, _7z_path, plugin_path))
# 记录到下载历史
self.main_window.download_queue_history.append(game_version)
def _start_ip_optimization(self, url):
"""开始IP优化过程
@@ -293,11 +371,33 @@ class DownloadManager:
# 显示优选结果
if not ip:
QtWidgets.QMessageBox.warning(
self.main_window,
f"优选失败 - {APP_NAME}",
"\n未能找到合适的Cloudflare IP将使用默认网络进行下载。\n"
)
msg_box = QtWidgets.QMessageBox(self.main_window)
msg_box.setWindowTitle(f"优选失败 - {APP_NAME}")
msg_box.setText("\n未能找到合适的Cloudflare IP将使用默认网络进行下载。\n\n10秒后自动继续...")
msg_box.setIcon(QtWidgets.QMessageBox.Icon.Warning)
ok_button = msg_box.addButton("确定 (10)", QtWidgets.QMessageBox.ButtonRole.AcceptRole)
# 创建计时器实现倒计时
countdown = 10
timer = QtCore.QTimer(self.main_window)
def update_countdown():
nonlocal countdown
countdown -= 1
ok_button.setText(f"确定 ({countdown})")
if countdown <= 0:
timer.stop()
if msg_box.isVisible():
msg_box.accept()
timer.timeout.connect(update_countdown)
timer.start(1000) # 每秒更新一次
# 显示对话框,但不阻塞主线程
msg_box.open()
# 连接关闭信号以停止计时器
msg_box.finished.connect(timer.stop)
else:
# 应用优选IP到hosts文件
if self.download_queue:
@@ -308,11 +408,33 @@ class DownloadManager:
self.hosts_manager.clean_hostname_entries(hostname)
if self.hosts_manager.apply_ip(hostname, ip):
QtWidgets.QMessageBox.information(
self.main_window,
f"成功 - {APP_NAME}",
f"\n已将优选IP ({ip}) 应用到hosts文件。\n"
)
msg_box = QtWidgets.QMessageBox(self.main_window)
msg_box.setWindowTitle(f"成功 - {APP_NAME}")
msg_box.setText(f"\n已将优选IP ({ip}) 应用到hosts文件。\n\n10秒后自动继续...")
msg_box.setIcon(QtWidgets.QMessageBox.Icon.Information)
ok_button = msg_box.addButton("确定 (10)", QtWidgets.QMessageBox.ButtonRole.AcceptRole)
# 创建计时器实现倒计时
countdown = 10
timer = QtCore.QTimer(self.main_window)
def update_countdown():
nonlocal countdown
countdown -= 1
ok_button.setText(f"确定 ({countdown})")
if countdown <= 0:
timer.stop()
if msg_box.isVisible():
msg_box.accept()
timer.timeout.connect(update_countdown)
timer.start(1000) # 每秒更新一次
# 显示对话框,但不阻塞主线程
msg_box.open()
# 连接关闭信号以停止计时器
msg_box.finished.connect(timer.stop)
else:
QtWidgets.QMessageBox.critical(
self.main_window,
@@ -320,8 +442,8 @@ class DownloadManager:
"\n修改hosts文件失败请检查程序是否以管理员权限运行。\n"
)
# 开始下载
self.next_download_task()
# 计时器结束或用户点击确定时,继续下载
QtCore.QTimer.singleShot(10000, self.next_download_task)
def next_download_task(self):
"""处理下载队列中的下一个任务"""
@@ -347,21 +469,109 @@ class DownloadManager:
_7z_path: 7z文件保存路径
plugin_path: 插件路径
"""
game_exe = {
game: os.path.join(
self.selected_folder, info["install_path"].split("/")[0], info["exe"]
# 使用改进的目录识别获取安装路径
install_paths = self.get_install_paths()
debug_mode = self.is_debug_mode()
if debug_mode:
print(f"DEBUG: 准备下载游戏 {game_version}")
print(f"DEBUG: 游戏文件夹: {game_folder}")
# 获取游戏可执行文件路径
game_dirs = self.main_window.identify_game_directories_improved(self.selected_folder)
game_exe_exists = False
if game_version in game_dirs:
game_dir = game_dirs[game_version]
# 游戏目录已经通过可执行文件验证了,可以直接认为存在
game_exe_exists = True
if debug_mode:
print(f"DEBUG: 游戏目录已验证: {game_dir}")
print(f"DEBUG: 游戏可执行文件存在: {game_exe_exists}")
else:
# 回退到传统方法检查游戏是否存在
# 尝试多种可能的文件名格式
expected_exe = GAME_INFO[game_version]["exe"]
traditional_folder = os.path.join(
self.selected_folder,
GAME_INFO[game_version]["install_path"].split("/")[0]
)
for game, info in GAME_INFO.items()
}
# 定义多种可能的可执行文件变体
exe_variants = [
expected_exe, # 标准文件名
expected_exe + ".nocrack", # Steam加密版本
expected_exe.replace(".exe", ""), # 无扩展名版本
expected_exe.replace("NEKOPARA", "nekopara").lower(), # 全小写变体
expected_exe.lower(), # 小写变体
expected_exe.lower() + ".nocrack", # 小写变体的Steam加密版本
]
# 对于Vol.3可能有特殊名称
if "Vol.3" in game_version:
# 增加可能的卷3特定的变体
exe_variants.extend([
"NEKOPARAVol3.exe",
"NEKOPARAVol3.exe.nocrack",
"nekoparavol3.exe",
"nekoparavol3.exe.nocrack",
"nekopara_vol3.exe",
"nekopara_vol3.exe.nocrack",
"vol3.exe",
"vol3.exe.nocrack"
])
# 检查所有可能的文件名
for exe_variant in exe_variants:
exe_path = os.path.join(traditional_folder, exe_variant)
if os.path.exists(exe_path):
game_exe_exists = True
if debug_mode:
print(f"DEBUG: 找到游戏可执行文件: {exe_path}")
break
# 如果仍未找到,尝试递归搜索
if not game_exe_exists and os.path.exists(traditional_folder):
# 提取卷号或检查是否是After
vol_match = re.search(r"Vol\.(\d+)", game_version)
vol_num = None
if vol_match:
vol_num = vol_match.group(1)
is_after = "After" in game_version
# 遍历游戏目录及其子目录
for root, dirs, files in os.walk(traditional_folder):
for file in files:
file_lower = file.lower()
if file.endswith('.exe') or file.endswith('.exe.nocrack'):
# 检查文件名中是否包含卷号或关键词
if ((vol_num and (f"vol{vol_num}" in file_lower or
f"vol.{vol_num}" in file_lower or
f"vol {vol_num}" in file_lower)) or
(is_after and "after" in file_lower)):
game_exe_exists = True
if debug_mode:
print(f"DEBUG: 通过递归搜索找到游戏可执行文件: {os.path.join(root, file)}")
break
if game_exe_exists:
break
if debug_mode:
print(f"DEBUG: 使用传统方法检查游戏目录: {traditional_folder}")
print(f"DEBUG: 游戏可执行文件存在: {game_exe_exists}")
# 检查游戏是否已安装
if (
game_version not in game_exe
or not os.path.exists(game_exe[game_version])
not game_exe_exists
or self.main_window.installed_status[game_version]
):
self.main_window.installed_status[game_version] = False
self.main_window.show_result()
if debug_mode:
print(f"DEBUG: 跳过下载游戏 {game_version}")
print(f"DEBUG: 游戏存在: {game_exe_exists}")
print(f"DEBUG: 已安装补丁: {self.main_window.installed_status[game_version]}")
self.main_window.installed_status[game_version] = False if not game_exe_exists else True
self.next_download_task()
return
# 创建进度窗口并开始下载
@@ -450,7 +660,7 @@ class DownloadManager:
return
# 下载成功,开始解压缩
self.main_window.hash_msg_box = self.main_window.hash_manager.hash_pop_window()
self.main_window.hash_msg_box = self.main_window.hash_manager.hash_pop_window(check_type="extraction")
# 创建并启动解压线程
self.main_window.extraction_thread = self.main_window.create_extraction_thread(
@@ -510,6 +720,6 @@ class DownloadManager:
# 重新启用退出按钮和开始安装按钮
self.main_window.ui.exit_btn.setEnabled(True)
self.main_window.ui.start_install_btn.setEnabled(True)
self.main_window.set_start_button_enabled(True)
self.main_window.show_result()

View File

@@ -1,11 +1,10 @@
from PySide6.QtGui import QIcon, QAction
from PySide6.QtGui import QIcon, QAction, QFont
from PySide6.QtWidgets import QMessageBox, QMainWindow
from PySide6.QtCore import Qt
import webbrowser
from utils import load_base64_image, msgbox_frame
from data.config import APP_NAME, APP_VERSION
from data.pic_data import img_data
class UIManager:
def __init__(self, main_window):
@@ -22,10 +21,11 @@ class UIManager:
def setup_ui(self):
"""设置UI元素包括窗口图标、标题和菜单"""
# 设置窗口图标
icon_data = img_data.get("icon")
if icon_data:
pixmap = load_base64_image(icon_data)
self.main_window.setWindowIcon(QIcon(pixmap))
import os
from utils import resource_path
icon_path = resource_path(os.path.join("IMG", "ICO", "icon.png"))
if os.path.exists(icon_path):
self.main_window.setWindowIcon(QIcon(icon_path))
# 设置窗口标题
self.main_window.setWindowTitle(f"{APP_NAME} v{APP_VERSION}")
@@ -39,20 +39,28 @@ class UIManager:
if not self.ui or not hasattr(self.ui, 'menu_2'):
return
# 创建菜单项
project_home_action = QAction("项目主页", self.main_window)
project_home_action.triggered.connect(self.open_project_home_page)
about_action = QAction("关于", self.main_window)
about_action.triggered.connect(self.show_about_dialog)
# 添加到菜单
self.ui.menu_2.addAction(project_home_action)
self.ui.menu_2.addAction(about_action)
# 连接按钮点击事件,如果使用按钮式菜单
if hasattr(self.ui, 'help_btn'):
# 按钮已经连接到显示菜单,不需要额外处理
pass
def _setup_settings_menu(self):
"""设置"设置"菜单"""
if not self.ui or not hasattr(self.ui, 'menu'):
return
# 创建菜单项
self.debug_action = QAction("Debug模式", self.main_window, checkable=True)
# 安全地获取config属性
@@ -67,13 +75,22 @@ class UIManager:
if hasattr(self.main_window, 'toggle_debug_mode'):
self.debug_action.triggered.connect(self.main_window.toggle_debug_mode)
# 添加到菜单
self.ui.menu.addAction(self.debug_action)
# 为未来功能预留的"切换下载源"按钮
self.switch_source_action = QAction("切换下载源", self.main_window)
self.switch_source_action.setEnabled(False) # 暂时禁用
self.ui.menu.addAction(self.switch_source_action)
# 添加分隔符
self.ui.menu.addSeparator()
# 连接按钮点击事件,如果使用按钮式菜单
if hasattr(self.ui, 'settings_btn'):
# 按钮已经连接到显示菜单,不需要额外处理
pass
def open_project_home_page(self):
"""打开项目主页"""
webbrowser.open("https://github.com/hyb-oyqq/FRAISEMOE-Addons-Installer-NEXT")

Binary file not shown.

View File

@@ -5,8 +5,10 @@ import json
import webbrowser
from PySide6 import QtWidgets
from PySide6.QtCore import QTimer
from PySide6.QtWidgets import QMainWindow, QMessageBox
from PySide6.QtCore import QTimer, Qt, QPoint, QRect, QSize
from PySide6.QtWidgets import QMainWindow, QMessageBox, QGraphicsOpacityEffect, QGraphicsColorizeEffect
from PySide6.QtGui import QPalette, QColor, QPainterPath, QRegion
from PySide6.QtGui import QAction # Added for menu actions
from ui.Ui_install import Ui_MainWindows
from data.config import (
@@ -14,7 +16,7 @@ from data.config import (
PLUGIN_HASH, UA, CONFIG_URL, LOG_FILE
)
from utils import (
load_config, save_config, HashManager, AdminPrivileges, msgbox_frame
load_config, save_config, HashManager, AdminPrivileges, msgbox_frame, load_image_from_file
)
from workers import (
DownloadThread, ProgressWindow, IpOptimizerThread,
@@ -28,10 +30,31 @@ class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
# 设置窗口为无边框
self.setWindowFlags(Qt.WindowType.FramelessWindowHint)
# 设置窗口背景透明
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
# 调整窗口大小以适应背景图片比例 (1280x720)
self.resize(1280, 720)
# 设置固定尺寸范围
self.setMinimumSize(QSize(1024, 576))
self.setMaximumSize(QSize(1280, 720))
# 窗口比例 (16:9)
self.aspect_ratio = 16 / 9
# 拖动窗口相关变量
self._drag_position = QPoint()
self._is_dragging = False
# 初始化UI (从Ui_install.py导入)
self.ui = Ui_MainWindows()
self.ui.setupUi(self)
# 设置圆角窗口
self.setRoundedCorners()
# 初始化配置
self.config = load_config()
@@ -57,10 +80,12 @@ class MainWindow(QMainWindow):
self.debug_manager = DebugManager(self)
self.download_manager = DownloadManager(self)
# 设置退出按钮和开始安装按钮的样式表,使其在禁用状态下不会变灰
button_style = "QPushButton:disabled { opacity: 1.0; }"
self.ui.exit_btn.setStyleSheet(button_style)
self.ui.start_install_btn.setStyleSheet(button_style)
# 设置关闭按钮事件连接
if hasattr(self.ui, 'close_btn'):
self.ui.close_btn.clicked.connect(self.close)
if hasattr(self.ui, 'minimize_btn'):
self.ui.minimize_btn.clicked.connect(self.showMinimized)
# 检查管理员权限和进程
self.admin_privileges.request_admin_privileges()
@@ -81,10 +106,15 @@ class MainWindow(QMainWindow):
)
sys.exit(1)
# 连接信号
self.ui.start_install_btn.clicked.connect(self.download_manager.file_dialog)
# 连接信号 - 绑定到新按钮
self.ui.start_install_btn.clicked.connect(self.handle_install_button_click)
self.ui.uninstall_btn.clicked.connect(self.handle_uninstall_button_click) # 添加卸载补丁按钮事件连接
self.ui.exit_btn.clicked.connect(self.shutdown_app)
# 初始化按钮状态标记
self.install_button_enabled = False
self.last_error_message = ""
# 根据初始配置决定是否开启Debug模式
if hasattr(self.ui_manager, 'debug_action') and self.ui_manager.debug_action:
if self.ui_manager.debug_action.isChecked():
@@ -96,14 +126,116 @@ class MainWindow(QMainWindow):
# 窗口显示后延迟100ms启动动画
QTimer.singleShot(100, self.start_animations)
def setRoundedCorners(self):
"""设置窗口圆角"""
# 实现圆角窗口
path = QPainterPath()
path.addRoundedRect(self.rect(), 20, 20)
mask = QRegion(path.toFillPolygon().toPolygon())
self.setMask(mask)
# 更新resize事件时更新圆角
self.updateRoundedCorners = True
# 添加鼠标事件处理,实现窗口拖动
def mousePressEvent(self, event):
if event.button() == Qt.MouseButton.LeftButton:
# 只有当鼠标在标题栏区域时才可以拖动
if hasattr(self.ui, 'title_bar') and self.ui.title_bar.geometry().contains(event.position().toPoint()):
self._is_dragging = True
self._drag_position = event.globalPosition().toPoint() - self.frameGeometry().topLeft()
event.accept()
def mouseMoveEvent(self, event):
if event.buttons() & Qt.MouseButton.LeftButton and self._is_dragging:
self.move(event.globalPosition().toPoint() - self._drag_position)
event.accept()
def mouseReleaseEvent(self, event):
if event.button() == Qt.MouseButton.LeftButton:
self._is_dragging = False
event.accept()
def resizeEvent(self, event):
"""当窗口大小改变时更新圆角和维持纵横比"""
# 计算基于当前宽度的合适高度以维持16:9比例
new_width = event.size().width()
new_height = int(new_width / self.aspect_ratio)
if new_height != event.size().height():
# 阻止变形,保持比例
self.resize(new_width, new_height)
# 更新主容器大小
if hasattr(self.ui, 'main_container'):
self.ui.main_container.setGeometry(0, 0, new_width, new_height)
# 更新内容容器大小
if hasattr(self.ui, 'content_container'):
self.ui.content_container.setGeometry(0, 0, new_width, new_height)
# 更新标题栏宽度和高度
if hasattr(self.ui, 'title_bar'):
self.ui.title_bar.setGeometry(0, 0, new_width, 35)
# 更新菜单区域
if hasattr(self.ui, 'menu_area'):
self.ui.menu_area.setGeometry(0, 35, new_width, 30)
# 更新内容区域大小
if hasattr(self.ui, 'inner_content'):
self.ui.inner_content.setGeometry(0, 65, new_width, new_height - 65)
# 更新背景图大小 - 使用setScaledContents简化处理
if hasattr(self.ui, 'Mainbg'):
self.ui.Mainbg.setGeometry(0, 0, new_width, new_height - 65)
# 使用setScaledContents=True不需要手动缩放
if hasattr(self.ui, 'loadbg'):
self.ui.loadbg.setGeometry(0, 0, new_width, new_height - 65)
# 调整按钮位置 - 固定在右侧
right_margin = 20 # 减小右边距,使按钮更靠右
if hasattr(self.ui, 'button_container'):
btn_width = 211 # 扩大后的容器宽度
btn_height = 111 # 扩大后的容器高度
x_pos = new_width - btn_width - right_margin
y_pos = int((new_height - 65) * 0.28) - 10 # 调整为更靠上的位置
self.ui.button_container.setGeometry(x_pos, y_pos, btn_width, btn_height)
# 添加卸载补丁按钮容器的位置调整
if hasattr(self.ui, 'uninstall_container'):
btn_width = 211 # 扩大后的容器宽度
btn_height = 111 # 扩大后的容器高度
x_pos = new_width - btn_width - right_margin
y_pos = int((new_height - 65) * 0.46) - 10 # 调整为中间位置
self.ui.uninstall_container.setGeometry(x_pos, y_pos, btn_width, btn_height)
if hasattr(self.ui, 'exit_container'):
btn_width = 211 # 扩大后的容器宽度
btn_height = 111 # 扩大后的容器高度
x_pos = new_width - btn_width - right_margin
y_pos = int((new_height - 65) * 0.64) - 10 # 调整为更靠下的位置
self.ui.exit_container.setGeometry(x_pos, y_pos, btn_width, btn_height)
# 更新圆角
if hasattr(self, 'updateRoundedCorners') and self.updateRoundedCorners:
path = QPainterPath()
path.addRoundedRect(self.rect(), 20, 20)
mask = QRegion(path.toFillPolygon().toPolygon())
self.setMask(mask)
super().resizeEvent(event)
def start_animations(self):
"""开始启动动画"""
# 不再禁用退出按钮的交互性,只通过样式表控制外观
# 但仍然需要跟踪动画状态,防止用户在动画播放过程中退出
self.animation_in_progress = True
# 禁用开始安装按钮,防止在动画播放期间点击
self.ui.start_install_btn.setEnabled(False)
# 按钮容器初始是隐藏的,无需在这里禁用
# 但确保开始安装按钮仍然处于禁用状态
self.set_start_button_enabled(False)
self.animator.animation_finished.connect(self.on_animations_finished)
self.animator.start_animations()
@@ -115,9 +247,26 @@ class MainWindow(QMainWindow):
# 只有在配置有效时才启用开始安装按钮
if self.config_valid:
self.ui.start_install_btn.setEnabled(True)
self.set_start_button_enabled(True)
else:
self.ui.start_install_btn.setEnabled(False)
self.set_start_button_enabled(False)
def set_start_button_enabled(self, enabled):
"""设置开始安装按钮的启用状态和视觉效果
Args:
enabled: 是否启用按钮
"""
self.ui.start_install_btn.setEnabled(True) # 始终启用按钮,以便捕获点击事件
# 根据状态修改文本内容,但不再修改颜色样式
if enabled:
self.ui.start_install_text.setText("开始安装")
else:
self.ui.start_install_text.setText("!无法安装!")
# 记录当前按钮状态,用于点击事件处理
self.install_button_enabled = enabled
def fetch_cloud_config(self):
"""获取云端配置"""
@@ -134,11 +283,16 @@ class MainWindow(QMainWindow):
data: 获取到的配置数据
error_message: 错误信息,如果有
"""
# 定义debug_mode变量在方法开头
debug_mode = self.ui_manager.debug_action.isChecked() if hasattr(self.ui_manager, 'debug_action') and self.ui_manager.debug_action else False
if error_message:
# 标记配置无效
self.config_valid = False
# 记录错误信息,用于按钮点击时显示
if error_message == "update_required":
self.last_error_message = "update_required"
msg_box = msgbox_frame(
f"更新提示 - {APP_NAME}",
"\n当前版本过低,请及时更新。\n",
@@ -151,6 +305,7 @@ class MainWindow(QMainWindow):
self.shutdown_app(force_exit=True)
return
elif "missing_keys" in error_message:
self.last_error_message = "missing_keys"
missing_versions = error_message.split(":")[1]
msg_box = msgbox_frame(
f"配置缺失 - {APP_NAME}",
@@ -161,8 +316,10 @@ class MainWindow(QMainWindow):
# 对于部分缺失,仍然允许使用,因为可能只影响部分游戏版本
self.config_valid = True
else:
# 设置网络错误标记
self.last_error_message = "network_error"
# 显示通用错误消息只在debug模式下显示详细错误
debug_mode = self.ui_manager.debug_action.isChecked() if self.ui_manager.debug_action else False
error_msg = "访问云端配置失败,请检查网络状况或稍后再试。"
if debug_mode and "详细错误:" in error_message:
msg_box = msgbox_frame(
@@ -178,18 +335,19 @@ class MainWindow(QMainWindow):
)
msg_box.exec()
# 在无法连接到云端时禁用开始安装按钮
self.ui.start_install_btn.setEnabled(False)
self.set_start_button_enabled(False)
else:
self.cloud_config = data
# 标记配置有效
self.config_valid = True
# 清除错误信息
self.last_error_message = ""
debug_mode = self.ui_manager.debug_action.isChecked() if self.ui_manager.debug_action else False
if debug_mode:
print("--- Cloud config fetched successfully ---")
print(json.dumps(data, indent=2))
# 确保按钮在成功获取配置时启用
self.ui.start_install_btn.setEnabled(True)
self.set_start_button_enabled(True)
def toggle_debug_mode(self, checked):
"""切换调试模式
@@ -255,7 +413,7 @@ class MainWindow(QMainWindow):
# 禁用退出按钮
self.ui.exit_btn.setEnabled(False)
self.hash_msg_box = self.hash_manager.hash_pop_window()
self.hash_msg_box = self.hash_manager.hash_pop_window(check_type="after")
install_paths = self.download_manager.get_install_paths()
@@ -293,24 +451,71 @@ class MainWindow(QMainWindow):
# 重新启用退出按钮和开始安装按钮
self.ui.exit_btn.setEnabled(True)
self.ui.start_install_btn.setEnabled(True)
self.set_start_button_enabled(True)
# 添加短暂延迟确保UI更新
QTimer.singleShot(100, self.show_result)
def show_result(self):
"""显示安装结果"""
installed_version = "\n".join(
[i for i in self.installed_status if self.installed_status[i]]
)
failed_ver = "\n".join(
[i for i in self.installed_status if not self.installed_status[i]]
)
"""显示安装结果,区分不同情况"""
# 获取当前安装状态
installed_versions = [] # 成功安装的版本
skipped_versions = [] # 已有补丁跳过的版本
failed_versions = [] # 安装失败的版本
not_found_versions = [] # 未找到的版本
# 获取所有游戏版本路径
install_paths = self.download_manager.get_install_paths() if hasattr(self.download_manager, "get_install_paths") else {}
for game_version, is_installed in self.installed_status.items():
path = install_paths.get(game_version, "")
# 检查游戏是否存在但未通过本次安装补丁
if is_installed:
# 游戏已安装补丁
if hasattr(self, 'download_queue_history') and game_version not in self.download_queue_history:
# 已有补丁,被跳过下载
skipped_versions.append(game_version)
else:
# 本次成功安装
installed_versions.append(game_version)
else:
# 游戏未安装补丁
if os.path.exists(path):
# 游戏文件夹存在,但安装失败
failed_versions.append(game_version)
else:
# 游戏文件夹不存在
not_found_versions.append(game_version)
# 构建结果信息
result_text = f"\n安装结果:\n"
# 总数统计
total_installed = len(installed_versions)
total_skipped = len(skipped_versions)
total_failed = len(failed_versions)
result_text += f"安装成功:{total_installed} 个 已跳过:{total_skipped} 个 安装失败:{total_failed}\n\n"
# 详细列表
if installed_versions:
result_text += f"【成功安装】:\n{chr(10).join(installed_versions)}\n\n"
if skipped_versions:
result_text += f"【已安装跳过】:\n{chr(10).join(skipped_versions)}\n\n"
if failed_versions:
result_text += f"【安装失败】:\n{chr(10).join(failed_versions)}\n\n"
if not_found_versions and (installed_versions or failed_versions):
# 只有当有其他版本存在时,才显示未找到的版本
result_text += f"【未在指定目录找到】:\n{chr(10).join(not_found_versions)}\n"
QMessageBox.information(
self,
f"完成 - {APP_NAME}",
f"\n安装结果:\n安装成功数:{len(installed_version.splitlines())} 安装失败数:{len(failed_ver.splitlines())}\n"
f"安装成功的版本:\n{installed_version}\n尚未持有或未使用本工具安装补丁的版本:\n{failed_ver}\n",
f"安装完成 - {APP_NAME}",
result_text
)
def closeEvent(self, event):
@@ -378,4 +583,617 @@ class MainWindow(QMainWindow):
if event:
event.accept()
else:
sys.exit(0)
sys.exit(0)
def handle_install_button_click(self):
"""处理安装按钮点击事件
根据按钮当前状态决定是显示错误还是执行安装
"""
if not self.install_button_enabled:
# 按钮处于"无法安装"状态
if self.last_error_message == "update_required":
msg_box = msgbox_frame(
f"更新提示 - {APP_NAME}",
"\n当前版本过低,请及时更新。\n",
QMessageBox.StandardButton.Ok,
)
msg_box.exec()
elif self.last_error_message == "directory_not_found":
# 目录识别失败的特定错误信息
reply = msgbox_frame(
f"目录错误 - {APP_NAME}",
"\n未能识别游戏目录请确认选择的是游戏的上级目录并且目录中包含Nekopara游戏文件夹。\n\n是否重新选择目录?\n",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
)
if reply.exec() == QMessageBox.StandardButton.Yes:
# 重新启用按钮并允许用户选择目录
self.set_start_button_enabled(True)
# 直接调用文件对话框
self.download_manager.file_dialog()
else:
# 网络错误或其他错误
reply = msgbox_frame(
f"错误 - {APP_NAME}",
"\n访问云端配置失败,请检查网络状况或稍后再试。\n\n是否重新尝试连接?\n",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
)
if reply.exec() == QMessageBox.StandardButton.Yes:
# 重试获取配置
self.fetch_cloud_config()
else:
# 按钮处于"开始安装"状态,正常执行安装流程
self.download_manager.file_dialog()
def handle_uninstall_button_click(self):
"""处理卸载补丁按钮点击事件
打开文件选择对话框选择游戏目录,然后卸载对应游戏的补丁
"""
# 获取游戏目录
from PySide6.QtWidgets import QFileDialog
debug_mode = self.ui_manager.debug_action.isChecked() if hasattr(self.ui_manager, 'debug_action') and self.ui_manager.debug_action else False
# 提示用户选择目录
file_dialog_info = "选择游戏上级目录" if debug_mode else "选择游戏目录"
selected_folder = QFileDialog.getExistingDirectory(self, file_dialog_info, "")
if not selected_folder or selected_folder == "":
return # 用户取消了选择
if debug_mode:
print(f"DEBUG: 卸载功能 - 用户选择了目录: {selected_folder}")
# 首先尝试将选择的目录视为上级目录,使用增强的目录识别功能
game_dirs = self.identify_game_directories_improved(selected_folder)
if game_dirs and len(game_dirs) > 0:
# 找到了游戏目录,显示选择对话框
if debug_mode:
print(f"DEBUG: 卸载功能 - 在上级目录中找到以下游戏: {list(game_dirs.keys())}")
# 如果只有一个游戏,直接选择它
if len(game_dirs) == 1:
game_version = list(game_dirs.keys())[0]
game_dir = game_dirs[game_version]
self._confirm_and_uninstall(game_dir, game_version)
else:
# 有多个游戏,让用户选择
from PySide6.QtWidgets import QInputDialog
game_versions = list(game_dirs.keys())
# 添加"全部卸载"选项
game_versions.append("全部卸载")
selected_game, ok = QInputDialog.getItem(
self, "选择游戏", "选择要卸载补丁的游戏:",
game_versions, 0, False
)
if ok and selected_game:
if selected_game == "全部卸载":
# 卸载所有游戏补丁
reply = QMessageBox.question(
self,
f"确认卸载 - {APP_NAME}",
f"\n确定要卸载所有游戏的补丁吗?\n这将卸载以下游戏的补丁:\n{chr(10).join(list(game_dirs.keys()))}\n",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
QMessageBox.StandardButton.No
)
if reply == QMessageBox.StandardButton.Yes:
success_count = 0
fail_count = 0
for version, path in game_dirs.items():
try:
if self.uninstall_patch(path, version):
success_count += 1
else:
fail_count += 1
except Exception as e:
if debug_mode:
print(f"DEBUG: 卸载 {version} 时出错: {str(e)}")
fail_count += 1
# 显示批量卸载结果
QMessageBox.information(
self,
f"批量卸载完成 - {APP_NAME}",
f"\n批量卸载完成!\n成功: {success_count}\n失败: {fail_count}\n",
QMessageBox.StandardButton.Ok,
)
else:
# 卸载选中的单个游戏
game_version = selected_game
game_dir = game_dirs[game_version]
self._confirm_and_uninstall(game_dir, game_version)
else:
# 未找到游戏目录,尝试将选择的目录作为游戏目录
if debug_mode:
print(f"DEBUG: 卸载功能 - 未在上级目录找到游戏,尝试将选择的目录视为游戏目录")
game_version = self.identify_game_version(selected_folder)
if game_version:
if debug_mode:
print(f"DEBUG: 卸载功能 - 识别为游戏: {game_version}")
self._confirm_and_uninstall(selected_folder, game_version)
else:
# 两种方式都未识别到游戏
if debug_mode:
print(f"DEBUG: 卸载功能 - 无法识别游戏")
msg_box = msgbox_frame(
f"错误 - {APP_NAME}",
"\n所选目录不是有效的NEKOPARA游戏目录。\n请选择包含游戏可执行文件的目录或其上级目录。\n",
QMessageBox.StandardButton.Ok,
)
msg_box.exec()
def _confirm_and_uninstall(self, game_dir, game_version):
"""确认并卸载补丁
Args:
game_dir: 游戏目录
game_version: 游戏版本
"""
debug_mode = self.ui_manager.debug_action.isChecked() if hasattr(self.ui_manager, 'debug_action') and self.ui_manager.debug_action else False
# 确认卸载
reply = QMessageBox.question(
self,
f"确认卸载 - {APP_NAME}",
f"\n确定要卸载 {game_version} 的补丁吗?\n游戏目录: {game_dir}\n",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
QMessageBox.StandardButton.No
)
if reply == QMessageBox.StandardButton.No:
return
# 开始卸载补丁
self.uninstall_patch(game_dir, game_version)
def identify_game_version(self, game_dir):
"""识别游戏版本
Args:
game_dir: 游戏目录路径
Returns:
str: 游戏版本名称如果不是有效的游戏目录则返回None
"""
import os
import re
# 调试模式
debug_mode = self.ui_manager.debug_action.isChecked() if hasattr(self.ui_manager, 'debug_action') and self.ui_manager.debug_action else False
if debug_mode:
print(f"DEBUG: 尝试识别游戏版本: {game_dir}")
# 先通过目录名称进行初步推测(这将作为递归搜索的提示)
dir_name = os.path.basename(game_dir).lower()
potential_version = None
vol_num = None
# 提取卷号或判断是否是After
if "vol" in dir_name or "vol." in dir_name:
vol_match = re.search(r"vol(?:\.|\s*)?(\d+)", dir_name)
if vol_match:
vol_num = vol_match.group(1)
potential_version = f"NEKOPARA Vol.{vol_num}"
if debug_mode:
print(f"DEBUG: 从目录名推测游戏版本: {potential_version}, 卷号: {vol_num}")
elif "after" in dir_name:
potential_version = "NEKOPARA After"
if debug_mode:
print(f"DEBUG: 从目录名推测游戏版本: NEKOPARA After")
# 检查是否为NEKOPARA游戏目录
# 通过检查游戏可执行文件来识别游戏版本
for game_version, info in GAME_INFO.items():
# 尝试多种可能的可执行文件名变体
exe_variants = [
info["exe"], # 标准文件名
info["exe"] + ".nocrack", # Steam加密版本
info["exe"].replace(".exe", ""), # 无扩展名版本
info["exe"].replace("NEKOPARA", "nekopara").lower(), # 全小写变体
info["exe"].lower(), # 小写变体
info["exe"].lower() + ".nocrack", # 小写变体的Steam加密版本
]
# 对于Vol.3可能有特殊名称
if "Vol.3" in game_version:
# 增加可能的卷3特定的变体
exe_variants.extend([
"NEKOPARAVol3.exe",
"NEKOPARAVol3.exe.nocrack",
"nekoparavol3.exe",
"nekoparavol3.exe.nocrack",
"nekopara_vol3.exe",
"nekopara_vol3.exe.nocrack",
"vol3.exe",
"vol3.exe.nocrack"
])
for exe_variant in exe_variants:
exe_path = os.path.join(game_dir, exe_variant)
if os.path.exists(exe_path):
if debug_mode:
print(f"DEBUG: 通过可执行文件确认游戏版本: {game_version}, 文件: {exe_variant}")
return game_version
# 如果没有直接匹配,尝试递归搜索
if potential_version:
# 从预测的版本中获取卷号或确认是否是After
is_after = "After" in potential_version
if not vol_num and not is_after:
vol_match = re.search(r"Vol\.(\d+)", potential_version)
if vol_match:
vol_num = vol_match.group(1)
# 递归搜索可执行文件
for root, dirs, files in os.walk(game_dir):
for file in files:
file_lower = file.lower()
if file.endswith('.exe') or file.endswith('.exe.nocrack'):
# 检查文件名中是否包含卷号或关键词
if ((vol_num and (f"vol{vol_num}" in file_lower or
f"vol.{vol_num}" in file_lower or
f"vol {vol_num}" in file_lower)) or
(is_after and "after" in file_lower)):
if debug_mode:
print(f"DEBUG: 通过递归搜索确认游戏版本: {potential_version}, 文件: {file}")
return potential_version
# 如果仍然没有找到,基于目录名的推测返回结果
if potential_version:
if debug_mode:
print(f"DEBUG: 基于目录名返回推测的游戏版本: {potential_version}")
return potential_version
if debug_mode:
print(f"DEBUG: 无法识别游戏版本: {game_dir}")
return None
def identify_game_directories_improved(self, selected_folder):
"""改进的游戏目录识别,支持大小写不敏感和特殊字符处理
Args:
selected_folder: 选择的上级目录
Returns:
dict: 游戏版本到游戏目录的映射
"""
import os
import re
# 添加debug日志
debug_mode = self.ui_manager.debug_action.isChecked() if hasattr(self.ui_manager, 'debug_action') and self.ui_manager.debug_action else False
if debug_mode:
print(f"--- 开始识别目录: {selected_folder} ---")
game_paths = {}
# 获取上级目录中的所有文件夹
try:
all_dirs = [d for d in os.listdir(selected_folder) if os.path.isdir(os.path.join(selected_folder, d))]
if debug_mode:
print(f"DEBUG: 找到以下子目录: {all_dirs}")
except Exception as e:
if debug_mode:
print(f"DEBUG: 无法读取目录 {selected_folder}: {str(e)}")
return {}
for game, info in GAME_INFO.items():
expected_dir = info["install_path"].split("/")[0] # 例如 "NEKOPARA Vol. 1"
expected_exe = info["exe"] # 标准可执行文件名
if debug_mode:
print(f"DEBUG: 搜索游戏 {game}, 预期目录: {expected_dir}, 预期可执行文件: {expected_exe}")
# 尝试不同的匹配方法
found_dir = None
# 1. 精确匹配
if expected_dir in all_dirs:
found_dir = expected_dir
if debug_mode:
print(f"DEBUG: 精确匹配成功: {expected_dir}")
# 2. 大小写不敏感匹配
if not found_dir:
for dir_name in all_dirs:
if expected_dir.lower() == dir_name.lower():
found_dir = dir_name
if debug_mode:
print(f"DEBUG: 大小写不敏感匹配成功: {dir_name}")
break
# 3. 更模糊的匹配(允许特殊字符差异)
if not found_dir:
# 准备用于模糊匹配的正则表达式模式
# 替换空格为可选空格或连字符,替换点为可选点
pattern_text = expected_dir.replace(" ", "[ -]?").replace(".", "\\.?")
pattern = re.compile(f"^{pattern_text}$", re.IGNORECASE)
for dir_name in all_dirs:
if pattern.match(dir_name):
found_dir = dir_name
if debug_mode:
print(f"DEBUG: 模糊匹配成功: {dir_name} 匹配模式 {pattern_text}")
break
# 4. 如果还是没找到,尝试更宽松的匹配
if not found_dir:
vol_match = re.search(r"vol(?:\.|\s*)?(\d+)", expected_dir, re.IGNORECASE)
vol_num = None
if vol_match:
vol_num = vol_match.group(1)
if debug_mode:
print(f"DEBUG: 提取卷号: {vol_num}")
is_after = "after" in expected_dir.lower()
for dir_name in all_dirs:
dir_lower = dir_name.lower()
# 对于After特殊处理
if is_after and "after" in dir_lower:
found_dir = dir_name
if debug_mode:
print(f"DEBUG: After特殊匹配成功: {dir_name}")
break
# 对于Vol特殊处理
if vol_num:
# 查找目录名中的卷号
dir_vol_match = re.search(r"vol(?:\.|\s*)?(\d+)", dir_lower)
if dir_vol_match and dir_vol_match.group(1) == vol_num:
found_dir = dir_name
if debug_mode:
print(f"DEBUG: 卷号匹配成功: {dir_name} 卷号 {vol_num}")
break
# 如果找到匹配的目录验证exe文件是否存在
if found_dir:
potential_path = os.path.join(selected_folder, found_dir)
# 尝试多种可能的可执行文件名变体
# 包括Steam加密版本和其他可能的变体
exe_variants = [
expected_exe, # 标准文件名
expected_exe + ".nocrack", # Steam加密版本
expected_exe.replace(".exe", ""),# 无扩展名版本
# Vol.3的特殊变体,因为它的文件名可能不一样
expected_exe.replace("NEKOPARA", "nekopara").lower(), # 全小写变体
expected_exe.lower(), # 小写变体
expected_exe.lower() + ".nocrack", # 小写变体的Steam加密版本
]
# 对于Vol.3可能有特殊名称
if "Vol.3" in game:
# 增加可能的卷3特定的变体
exe_variants.extend([
"NEKOPARAVol3.exe",
"NEKOPARAVol3.exe.nocrack",
"nekoparavol3.exe",
"nekoparavol3.exe.nocrack",
"nekopara_vol3.exe",
"nekopara_vol3.exe.nocrack",
"vol3.exe",
"vol3.exe.nocrack"
])
exe_exists = False
found_exe = None
# 尝试所有可能的变体
for exe_variant in exe_variants:
exe_path = os.path.join(potential_path, exe_variant)
if os.path.exists(exe_path):
exe_exists = True
found_exe = exe_variant
if debug_mode:
print(f"DEBUG: 验证成功,找到游戏可执行文件: {exe_variant}")
break
# 如果没有直接找到,尝试递归搜索当前目录下的所有可执行文件
if not exe_exists:
# 遍历当前目录下的所有文件和文件夹
for root, dirs, files in os.walk(potential_path):
for file in files:
file_lower = file.lower()
# 检查是否是游戏可执行文件(根据关键字)
if file.endswith('.exe') or file.endswith('.exe.nocrack'):
# 检查文件名中是否包含卷号或关键词
if (f"vol{vol_num}" in file_lower or
f"vol.{vol_num}" in file_lower or
f"vol {vol_num}" in file_lower or
(is_after and "after" in file_lower)):
exe_path = os.path.join(root, file)
exe_exists = True
found_exe = os.path.relpath(exe_path, potential_path)
if debug_mode:
print(f"DEBUG: 通过递归搜索找到游戏可执行文件: {found_exe}")
break
if exe_exists:
break
# 如果找到了可执行文件,将该目录添加到游戏目录列表
if exe_exists:
game_paths[game] = potential_path
if debug_mode:
print(f"DEBUG: 验证成功,将 {potential_path} 添加为 {game} 的目录")
else:
if debug_mode:
print(f"DEBUG: 未找到任何可执行文件变体,游戏 {game}{potential_path} 未找到")
if debug_mode:
print(f"DEBUG: 最终识别的游戏目录: {game_paths}")
print(f"--- 目录识别结束 ---")
return game_paths
def uninstall_patch(self, game_dir, game_version):
"""卸载补丁
Args:
game_dir: 游戏目录路径
game_version: 游戏版本
Returns:
bool: 卸载成功返回True失败返回False
"""
import os
import shutil
debug_mode = self.ui_manager.debug_action.isChecked() if hasattr(self.ui_manager, 'debug_action') and self.ui_manager.debug_action else False
if game_version not in GAME_INFO:
QMessageBox.critical(
self,
f"错误 - {APP_NAME}",
f"\n无法识别游戏版本: {game_version}\n",
QMessageBox.StandardButton.Ok,
)
return False
if debug_mode:
print(f"DEBUG: 开始卸载 {game_version} 补丁,目录: {game_dir}")
try:
files_removed = 0
# 获取可能的补丁文件路径
install_path_base = os.path.basename(GAME_INFO[game_version]["install_path"])
patch_file_path = os.path.join(game_dir, install_path_base)
# 尝试查找补丁文件,支持不同大小写
patch_files_to_check = [
patch_file_path,
patch_file_path.lower(),
patch_file_path.upper(),
patch_file_path.replace("_", ""),
patch_file_path.replace("_", "-"),
]
# 查找并删除补丁文件
patch_file_found = False
for patch_path in patch_files_to_check:
if os.path.exists(patch_path):
patch_file_found = True
os.remove(patch_path)
files_removed += 1
if debug_mode:
print(f"DEBUG: 已删除补丁文件: {patch_path}")
if not patch_file_found and debug_mode:
print(f"DEBUG: 未找到补丁文件,检查了以下路径: {patch_files_to_check}")
# 检查是否有额外的签名文件 (.sig)
if game_version == "NEKOPARA After":
for patch_path in patch_files_to_check:
sig_file_path = f"{patch_path}.sig"
if os.path.exists(sig_file_path):
os.remove(sig_file_path)
files_removed += 1
if debug_mode:
print(f"DEBUG: 已删除签名文件: {sig_file_path}")
# 删除patch文件夹
patch_folders_to_check = [
os.path.join(game_dir, "patch"),
os.path.join(game_dir, "Patch"),
os.path.join(game_dir, "PATCH"),
]
for patch_folder in patch_folders_to_check:
if os.path.exists(patch_folder):
shutil.rmtree(patch_folder)
files_removed += 1
if debug_mode:
print(f"DEBUG: 已删除补丁文件夹: {patch_folder}")
# 删除game/patch文件夹
game_folders = ["game", "Game", "GAME"]
patch_folders = ["patch", "Patch", "PATCH"]
for game_folder in game_folders:
for patch_folder in patch_folders:
game_patch_folder = os.path.join(game_dir, game_folder, patch_folder)
if os.path.exists(game_patch_folder):
shutil.rmtree(game_patch_folder)
files_removed += 1
if debug_mode:
print(f"DEBUG: 已删除game/patch文件夹: {game_patch_folder}")
# 删除配置文件
config_files = ["config.json", "Config.json", "CONFIG.JSON"]
script_files = ["scripts.json", "Scripts.json", "SCRIPTS.JSON"]
for game_folder in game_folders:
game_path = os.path.join(game_dir, game_folder)
if os.path.exists(game_path):
# 删除配置文件
for config_file in config_files:
config_path = os.path.join(game_path, config_file)
if os.path.exists(config_path):
os.remove(config_path)
files_removed += 1
if debug_mode:
print(f"DEBUG: 已删除配置文件: {config_path}")
# 删除脚本文件
for script_file in script_files:
script_path = os.path.join(game_path, script_file)
if os.path.exists(script_path):
os.remove(script_path)
files_removed += 1
if debug_mode:
print(f"DEBUG: 已删除脚本文件: {script_path}")
# 更新安装状态
self.installed_status[game_version] = False
# 在非批量卸载模式下显示卸载成功消息
if game_version != "all":
# 显示卸载成功消息
if files_removed > 0:
QMessageBox.information(
self,
f"卸载完成 - {APP_NAME}",
f"\n{game_version} 补丁卸载成功!\n共删除 {files_removed} 个文件/文件夹。\n",
QMessageBox.StandardButton.Ok,
)
else:
QMessageBox.warning(
self,
f"警告 - {APP_NAME}",
f"\n未找到 {game_version} 的补丁文件,可能未安装补丁或已被移除。\n",
QMessageBox.StandardButton.Ok,
)
# 卸载成功
return True
except Exception as e:
# 在非批量卸载模式下显示卸载失败消息
if game_version != "all":
# 显示卸载失败消息
error_message = f"\n卸载 {game_version} 补丁时出错:\n\n{str(e)}\n"
if debug_mode:
print(f"DEBUG: 卸载错误 - {str(e)}")
QMessageBox.critical(
self,
f"卸载失败 - {APP_NAME}",
error_message,
QMessageBox.StandardButton.Ok,
)
# 卸载失败
return False

View File

@@ -1,4 +1,3 @@
from data.pic_data import img_data
from PySide6.QtGui import QPixmap
import base64
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
@@ -8,105 +7,459 @@ from PySide6.QtGui import (QAction, QBrush, QColor, QConicalGradient,
QCursor, QFont, QFontDatabase, QGradient,
QIcon, QImage, QKeySequence, QLinearGradient,
QPainter, QPalette, QPixmap, QRadialGradient,
QTransform)
QTransform, QPainterPath, QRegion)
from PySide6.QtWidgets import (QApplication, QLabel, QMainWindow, QMenu,
QMenuBar, QPushButton, QSizePolicy, QWidget)
def load_base64_image(base64_str):
pixmap = QPixmap()
pixmap.loadFromData(base64.b64decode(base64_str))
return pixmap
QMenuBar, QPushButton, QSizePolicy, QWidget, QHBoxLayout)
import os
# 导入配置常量
from data.config import APP_NAME, APP_VERSION
from utils import load_image_from_file
class Ui_MainWindows(object):
def setupUi(self, MainWindows):
if not MainWindows.objectName():
MainWindows.setObjectName(u"MainWindows")
MainWindows.setEnabled(True)
MainWindows.resize(1024, 576)
MainWindows.setMinimumSize(QSize(1024, 576))
MainWindows.setMaximumSize(QSize(1024, 576))
# 调整窗口默认大小为1280x720以匹配背景图片
MainWindows.resize(1280, 720)
# 锁定窗口比例为16:9确保不会变形同时限制最大尺寸不超过背景图片
MainWindows.setMinimumSize(QSize(1024, 576)) # 16:9最小尺寸
MainWindows.setMaximumSize(QSize(1280, 720)) # 将最大尺寸限制为1280x720
# 设置固定纵横比
self.aspect_ratio = 16/9
MainWindows.setMouseTracking(False)
MainWindows.setTabletTracking(False)
MainWindows.setAcceptDrops(True)
MainWindows.setAutoFillBackground(True)
MainWindows.setAutoFillBackground(False)
MainWindows.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
MainWindows.setAnimated(True)
MainWindows.setDocumentMode(False)
MainWindows.setDockNestingEnabled(False)
# 加载自定义字体
font_id = QFontDatabase.addApplicationFont(os.path.join(os.path.dirname(os.path.dirname(__file__)), "fonts", "SmileySans-Oblique.ttf"))
font_family = QFontDatabase.applicationFontFamilies(font_id)[0] if font_id != -1 else "Arial"
self.custom_font = QFont(font_family, 16) # 创建字体对象大小为16
self.custom_font.setWeight(QFont.Weight.Medium) # 设置为中等粗细,不要太粗
self.centralwidget = QWidget(MainWindows)
self.centralwidget.setObjectName(u"centralwidget")
self.centralwidget.setAutoFillBackground(True)
self.loadbg = QLabel(self.centralwidget)
self.centralwidget.setAutoFillBackground(False) # 修改为False以支持透明背景
self.centralwidget.setStyleSheet("""
QWidget#centralwidget {
background-color: transparent;
}
""")
# 圆角背景容器
self.main_container = QWidget(self.centralwidget)
self.main_container.setObjectName(u"main_container")
self.main_container.setGeometry(QRect(0, 0, 1280, 720))
self.main_container.setStyleSheet("""
QWidget#main_container {
background-color: #E96948;
border-radius: 20px;
border: 1px solid #E96948;
}
""")
# 内容容器 - 用于限制内容在圆角范围内
self.content_container = QWidget(self.main_container)
self.content_container.setObjectName(u"content_container")
self.content_container.setGeometry(QRect(0, 0, 1280, 720))
self.content_container.setStyleSheet("""
QWidget#content_container {
background-color: transparent;
border-radius: 20px;
}
""")
# 添加圆角裁剪,确保内容在圆角范围内
rect = QRect(0, 0, 1280, 720)
path = QPainterPath()
path.addRoundedRect(rect, 20, 20)
region = QRegion(path.toFillPolygon().toPolygon())
self.content_container.setMask(region)
# 标题栏
self.title_bar = QWidget(self.content_container)
self.title_bar.setObjectName(u"title_bar")
self.title_bar.setGeometry(QRect(0, 0, 1280, 35)) # 减小高度从40到35
self.title_bar.setStyleSheet("""
QWidget#title_bar {
background-color: #E96948;
border-top-left-radius: 20px;
border-top-right-radius: 20px;
border-bottom: 1px solid #F47A5B;
}
""")
# 标题栏布局
self.title_layout = QHBoxLayout(self.title_bar)
self.title_layout.setSpacing(10)
self.title_layout.setContentsMargins(10, 0, 10, 0)
# 添加最小化和关闭按钮到标题栏
self.minimize_btn = QPushButton(self.title_bar)
self.minimize_btn.setObjectName(u"minimize_btn")
self.minimize_btn.setMinimumSize(QSize(24, 24))
self.minimize_btn.setMaximumSize(QSize(24, 24))
self.minimize_btn.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
self.minimize_btn.setStyleSheet("""
QPushButton {
background-color: #FFC107;
border-radius: 12px;
border: none;
}
QPushButton:hover {
background-color: #FFD54F;
}
QPushButton:pressed {
background-color: #FFA000;
}
""")
self.minimize_btn.setText("")
self.minimize_btn.setFont(QFont(font_family, 10))
self.close_btn = QPushButton(self.title_bar)
self.close_btn.setObjectName(u"close_btn")
self.close_btn.setMinimumSize(QSize(24, 24))
self.close_btn.setMaximumSize(QSize(24, 24))
self.close_btn.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
self.close_btn.setStyleSheet("""
QPushButton {
background-color: #F44336;
border-radius: 12px;
border: none;
color: white;
font-weight: bold;
}
QPushButton:hover {
background-color: #EF5350;
}
QPushButton:pressed {
background-color: #D32F2F;
}
""")
self.close_btn.setText("×")
self.close_btn.setFont(QFont(font_family, 14))
# 标题文本
self.title_label = QLabel(self.title_bar)
self.title_label.setObjectName(u"title_label")
# 直接使用APP_NAME并添加版本号
self.title_label.setText(f"{APP_NAME} v{APP_VERSION}")
title_font = QFont(font_family, 14) # 减小字体从16到14
title_font.setBold(True)
self.title_label.setFont(title_font)
self.title_label.setStyleSheet("color: #333333; padding-left: 10px;")
self.title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
# 添加按钮到标题栏布局
self.title_layout.addWidget(self.title_label)
self.title_layout.addStretch(1)
self.title_layout.addWidget(self.minimize_btn)
self.title_layout.addSpacing(5)
self.title_layout.addWidget(self.close_btn)
# 修改菜单区域 - 确保足够宽以容纳更多菜单项
self.menu_area = QWidget(self.content_container)
self.menu_area.setObjectName(u"menu_area")
self.menu_area.setGeometry(QRect(0, 35, 1280, 30)) # 调整位置从40到35高度从35到30
self.menu_area.setStyleSheet("""
QWidget#menu_area {
background-color: #E96948;
}
""")
# 不再使用菜单栏,改用普通按钮
# 创建菜单按钮字体
menu_font = QFont(font_family, 14) # 进一步减小字体大小到14
menu_font.setBold(True)
# 设置按钮
self.settings_btn = QPushButton("设置", self.menu_area)
self.settings_btn.setObjectName(u"settings_btn")
self.settings_btn.setGeometry(QRect(20, 1, 80, 28)) # 调整高度和Y位置
self.settings_btn.setFont(menu_font)
self.settings_btn.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
self.settings_btn.setStyleSheet("""
QPushButton {
background-color: transparent;
color: white;
border: none;
text-align: left;
padding-left: 10px;
}
QPushButton:hover {
background-color: #F47A5B;
border-radius: 4px;
}
QPushButton:pressed {
background-color: #D25A3C;
border-radius: 4px;
}
""")
# 帮助按钮
self.help_btn = QPushButton("帮助", self.menu_area)
self.help_btn.setObjectName(u"help_btn")
self.help_btn.setGeometry(QRect(120, 1, 80, 28)) # 调整高度和Y位置
self.help_btn.setFont(menu_font)
self.help_btn.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
self.help_btn.setStyleSheet(self.settings_btn.styleSheet())
# 将原来的菜单项移到全局,方便访问
self.menu = QMenu(self.content_container)
self.menu.setObjectName(u"menu")
self.menu.setTitle("设置")
self.menu.setFont(menu_font)
self.menu.setStyleSheet("""
QMenu {
background-color: #E96948;
color: white;
font-size: 16px;
font-weight: bold;
border: 1px solid #F47A5B;
padding: 8px;
border-radius: 6px;
margin-top: 2px;
}
QMenu::item {
padding: 6px 20px 6px 15px;
background-color: transparent;
min-width: 120px;
color: white;
}
QMenu::item:selected {
background-color: #F47A5B;
border-radius: 4px;
}
QMenu::separator {
height: 1px;
background-color: #F47A5B;
margin: 5px 15px;
}
""")
self.menu_2 = QMenu(self.content_container)
self.menu_2.setObjectName(u"menu_2")
self.menu_2.setTitle("帮助")
self.menu_2.setFont(menu_font)
self.menu_2.setStyleSheet(self.menu.styleSheet())
# 连接按钮点击事件到显示对应菜单
self.settings_btn.clicked.connect(lambda: self.show_menu(self.menu, self.settings_btn))
self.help_btn.clicked.connect(lambda: self.show_menu(self.menu_2, self.help_btn))
# 预留位置给未来可能的第三个按钮
# 第三个按钮可以这样添加:
# self.third_btn = QPushButton("第三项", self.menu_area)
# self.third_btn.setObjectName(u"third_btn")
# self.third_btn.setGeometry(QRect(320, 0, 120, 35))
# self.third_btn.setFont(menu_font)
# self.third_btn.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
# self.third_btn.setStyleSheet(self.settings_btn.styleSheet())
# self.third_btn.clicked.connect(lambda: self.show_menu(self.menu_3, self.third_btn))
# 内容子容器
self.inner_content = QWidget(self.content_container)
self.inner_content.setObjectName(u"inner_content")
# 确保宽度足够大,保证右侧元素完全显示
self.inner_content.setGeometry(QRect(0, 65, 1280, 655)) # 调整Y位置从75到65高度从645到655
self.inner_content.setStyleSheet("""
QWidget#inner_content {
background-color: transparent;
border-bottom-left-radius: 20px;
border-bottom-right-radius: 20px;
}
""")
# 添加底部圆角裁剪
inner_rect = QRect(0, 0, 1280, 665)
inner_path = QPainterPath()
inner_path.addRoundedRect(inner_rect, 20, 20)
inner_region = QRegion(inner_path.toFillPolygon().toPolygon())
self.inner_content.setMask(inner_region)
# 在主容器中添加背景和内容元素
# 修改loadbg使用title_bg1.png作为整个背景
# 原来的loadbg保持不变
self.loadbg = QLabel(self.inner_content)
self.loadbg.setObjectName(u"loadbg")
self.loadbg.setGeometry(QRect(0, 0, 1031, 561))
self.loadbg.setPixmap(load_base64_image(img_data["loadbg"]))
self.loadbg.setGeometry(QRect(0, 0, 1280, 655))
# 加载背景图并允许拉伸
bg_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "IMG", "BG", "bg1.jpg")
bg_pixmap = QPixmap(bg_path)
self.loadbg.setPixmap(bg_pixmap)
self.loadbg.setScaledContents(True)
self.vol1bg = QLabel(self.centralwidget)
self.vol1bg = QLabel(self.inner_content)
self.vol1bg.setObjectName(u"vol1bg")
self.vol1bg.setGeometry(QRect(0, 120, 93, 64))
self.vol1bg.setPixmap(load_base64_image(img_data["vol1"]))
self.vol1bg.setGeometry(QRect(0, 150, 93, 64))
# 直接加载图片文件
vol1_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "IMG", "LOGO", "vo01_logo.png")
self.vol1bg.setPixmap(QPixmap(vol1_path))
self.vol1bg.setScaledContents(True)
self.vol2bg = QLabel(self.centralwidget)
self.vol2bg = QLabel(self.inner_content)
self.vol2bg.setObjectName(u"vol2bg")
self.vol2bg.setGeometry(QRect(0, 180, 93, 64))
self.vol2bg.setPixmap(load_base64_image(img_data["vol2"]))
self.vol2bg.setGeometry(QRect(0, 210, 93, 64))
# 直接加载图片文件
vol2_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "IMG", "LOGO", "vo02_logo.png")
self.vol2bg.setPixmap(QPixmap(vol2_path))
self.vol2bg.setScaledContents(True)
self.vol3bg = QLabel(self.centralwidget)
self.vol3bg = QLabel(self.inner_content)
self.vol3bg.setObjectName(u"vol3bg")
self.vol3bg.setGeometry(QRect(0, 240, 93, 64))
self.vol3bg.setPixmap(load_base64_image(img_data["vol3"]))
self.vol3bg.setGeometry(QRect(0, 270, 93, 64))
# 直接加载图片文件
vol3_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "IMG", "LOGO", "vo03_logo.png")
self.vol3bg.setPixmap(QPixmap(vol3_path))
self.vol3bg.setScaledContents(True)
self.vol4bg = QLabel(self.centralwidget)
self.vol4bg = QLabel(self.inner_content)
self.vol4bg.setObjectName(u"vol4bg")
self.vol4bg.setGeometry(QRect(0, 300, 93, 64))
self.vol4bg.setPixmap(load_base64_image(img_data["vol4"]))
self.vol4bg.setGeometry(QRect(0, 330, 93, 64))
# 直接加载图片文件
vol4_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "IMG", "LOGO", "vo04_logo.png")
self.vol4bg.setPixmap(QPixmap(vol4_path))
self.vol4bg.setScaledContents(True)
self.afterbg = QLabel(self.centralwidget)
self.afterbg = QLabel(self.inner_content)
self.afterbg.setObjectName(u"afterbg")
self.afterbg.setGeometry(QRect(0, 360, 93, 64))
self.afterbg.setPixmap(load_base64_image(img_data["after"]))
self.afterbg.setGeometry(QRect(0, 390, 93, 64))
# 直接加载图片文件
after_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "IMG", "LOGO", "voaf_logo.png")
self.afterbg.setPixmap(QPixmap(after_path))
self.afterbg.setScaledContents(True)
self.Mainbg = QLabel(self.centralwidget)
# 修复Mainbg位置并使用title_bg1.png作为背景图片
self.Mainbg = QLabel(self.inner_content)
self.Mainbg.setObjectName(u"Mainbg")
self.Mainbg.setGeometry(QRect(0, 0, 1031, 561))
self.Mainbg.setPixmap(load_base64_image(img_data["Mainbg"]))
self.Mainbg.setScaledContents(True)
self.start_install_btn = QPushButton(self.centralwidget)
self.Mainbg.setGeometry(QRect(0, 0, 1280, 655))
# 允许拉伸以填满整个区域
main_bg_pixmap = load_image_from_file(os.path.join(os.path.dirname(os.path.dirname(__file__)), "IMG", "BG", "title_bg1.png"))
# 如果加载的图片不是空的,则设置,并允许拉伸填满
if not main_bg_pixmap.isNull():
self.Mainbg.setPixmap(main_bg_pixmap)
self.Mainbg.setScaledContents(True)
self.Mainbg.setAlignment(Qt.AlignmentFlag.AlignCenter)
# 使用新的按钮图片
button_pixmap = load_image_from_file(os.path.join(os.path.dirname(os.path.dirname(__file__)), "IMG", "BTN", "Button.png"))
# 创建文本标签布局的按钮
# 开始安装按钮 - 基于背景图片和标签组合
# 调整开始安装按钮的位置
self.button_container = QWidget(self.inner_content)
self.button_container.setObjectName(u"start_install_container")
self.button_container.setGeometry(QRect(1050, 200, 211, 111)) # 调整Y坐标上移至200
# 不要隐藏容器,让动画系统来控制它的可见性和位置
# 使用原来的按钮背景图片
self.start_install_bg = QLabel(self.button_container)
self.start_install_bg.setObjectName(u"start_install_bg")
self.start_install_bg.setGeometry(QRect(10, 10, 191, 91)) # 居中放置在扩大的容器中
self.start_install_bg.setPixmap(button_pixmap)
self.start_install_bg.setScaledContents(True)
self.start_install_text = QLabel(self.button_container)
self.start_install_text.setObjectName(u"start_install_text")
self.start_install_text.setGeometry(QRect(10, 7, 191, 91)) # 居中放置在扩大的容器中
self.start_install_text.setText("开始安装")
self.start_install_text.setFont(self.custom_font)
self.start_install_text.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.start_install_text.setStyleSheet("letter-spacing: 1px;")
# 点击区域透明按钮
self.start_install_btn = QPushButton(self.button_container)
self.start_install_btn.setObjectName(u"start_install_btn")
self.start_install_btn.setEnabled(True)
self.start_install_btn.setGeometry(QRect(780, 250, 191, 91))
self.start_install_btn.setAutoFillBackground(False)
start_install_icon = QIcon()
start_install_pixmap = load_base64_image(img_data["start_install_btn"])
if not start_install_pixmap.isNull():
start_install_icon.addPixmap(start_install_pixmap)
self.start_install_btn.setIcon(start_install_icon)
self.start_install_btn.setIcon(start_install_icon)
self.start_install_btn.setIconSize(QSize(189, 110))
self.start_install_btn.setCheckable(False)
self.start_install_btn.setAutoRepeat(False)
self.start_install_btn.setAutoDefault(False)
self.start_install_btn.setGeometry(QRect(10, 10, 191, 91)) # 居中放置在扩大的容器中
self.start_install_btn.setCursor(QCursor(Qt.CursorShape.PointingHandCursor)) # 设置鼠标悬停时为手形光标
self.start_install_btn.setFlat(True)
self.exit_btn = QPushButton(self.centralwidget)
self.start_install_btn.raise_() # 确保按钮在最上层
self.start_install_btn.setStyleSheet("""
QPushButton {
background-color: transparent;
border: none;
}
""")
# 添加卸载补丁按钮 - 新增
self.uninstall_container = QWidget(self.inner_content)
self.uninstall_container.setObjectName(u"uninstall_container")
self.uninstall_container.setGeometry(QRect(1050, 310, 211, 111)) # 调整Y坐标位于310位置
# 使用相同的按钮背景图片
self.uninstall_bg = QLabel(self.uninstall_container)
self.uninstall_bg.setObjectName(u"uninstall_bg")
self.uninstall_bg.setGeometry(QRect(10, 10, 191, 91)) # 居中放置在扩大的容器中
self.uninstall_bg.setPixmap(button_pixmap)
self.uninstall_bg.setScaledContents(True)
self.uninstall_text = QLabel(self.uninstall_container)
self.uninstall_text.setObjectName(u"uninstall_text")
self.uninstall_text.setGeometry(QRect(10, 7, 191, 91)) # 居中放置在扩大的容器中
self.uninstall_text.setText("卸载补丁")
self.uninstall_text.setFont(self.custom_font)
self.uninstall_text.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.uninstall_text.setStyleSheet("letter-spacing: 1px;")
# 点击区域透明按钮
self.uninstall_btn = QPushButton(self.uninstall_container)
self.uninstall_btn.setObjectName(u"uninstall_btn")
self.uninstall_btn.setGeometry(QRect(10, 10, 191, 91)) # 居中放置在扩大的容器中
self.uninstall_btn.setCursor(QCursor(Qt.CursorShape.PointingHandCursor)) # 设置鼠标悬停时为手形光标
self.uninstall_btn.setFlat(True)
self.uninstall_btn.raise_() # 确保按钮在最上层
self.uninstall_btn.setStyleSheet("""
QPushButton {
background-color: transparent;
border: none;
}
""")
# 退出按钮 - 基于背景图片和标签组合,调整位置
self.exit_container = QWidget(self.inner_content)
self.exit_container.setObjectName(u"exit_container")
self.exit_container.setGeometry(QRect(1050, 420, 211, 111)) # 调整Y坐标下移至420
# 不要隐藏容器,让动画系统来控制它的可见性和位置
# 使用原来的按钮背景图片
self.exit_bg = QLabel(self.exit_container)
self.exit_bg.setObjectName(u"exit_bg")
self.exit_bg.setGeometry(QRect(10, 10, 191, 91)) # 居中放置在扩大的容器中
self.exit_bg.setPixmap(button_pixmap)
self.exit_bg.setScaledContents(True)
self.exit_text = QLabel(self.exit_container)
self.exit_text.setObjectName(u"exit_text")
self.exit_text.setGeometry(QRect(10, 7, 191, 91)) # 居中放置在扩大的容器中
self.exit_text.setText("退出程序")
self.exit_text.setFont(self.custom_font)
self.exit_text.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.exit_text.setStyleSheet("letter-spacing: 1px;")
# 点击区域透明按钮
self.exit_btn = QPushButton(self.exit_container)
self.exit_btn.setObjectName(u"exit_btn")
self.exit_btn.setEnabled(True)
self.exit_btn.setGeometry(QRect(780, 340, 191, 91))
self.exit_btn.setAutoFillBackground(False)
exit_icon = QIcon()
exit_pixmap = load_base64_image(img_data["exit_btn"])
if not exit_pixmap.isNull():
exit_icon.addPixmap(exit_pixmap)
self.exit_btn.setIcon(exit_icon)
self.exit_btn.setIcon(exit_icon)
self.exit_btn.setIconSize(QSize(189, 110))
self.exit_btn.setCheckable(False)
self.exit_btn.setGeometry(QRect(10, 10, 191, 91)) # 居中放置在扩大的容器中
self.exit_btn.setCursor(QCursor(Qt.CursorShape.PointingHandCursor)) # 设置鼠标悬停时为手形光标
self.exit_btn.setFlat(True)
self.menubg = QLabel(self.centralwidget)
self.menubg.setObjectName(u"menubg")
self.menubg.setGeometry(QRect(710, 0, 321, 561))
self.menubg.setPixmap(load_base64_image(img_data["menubg"]))
self.menubg.setScaledContents(True)
self.exit_btn.raise_() # 确保按钮在最上层
self.exit_btn.setStyleSheet("""
QPushButton {
background-color: transparent;
border: none;
}
""")
MainWindows.setCentralWidget(self.centralwidget)
# 调整层级顺序
self.loadbg.raise_()
self.vol1bg.raise_()
self.vol2bg.raise_()
@@ -114,28 +467,22 @@ class Ui_MainWindows(object):
self.vol4bg.raise_()
self.afterbg.raise_()
self.Mainbg.raise_()
self.menubg.raise_()
self.start_install_btn.raise_()
self.exit_btn.raise_()
self.menubar = QMenuBar(MainWindows)
self.menubar.setObjectName(u"menubar")
self.menubar.setGeometry(QRect(0, 0, 1024, 21))
self.menu = QMenu(self.menubar)
self.menu.setObjectName(u"menu")
self.menu_2 = QMenu(self.menubar)
self.menu_2.setObjectName(u"menu_2")
MainWindows.setMenuBar(self.menubar)
self.menubar.addAction(self.menu.menuAction())
self.menubar.addAction(self.menu_2.menuAction())
self.menu.addSeparator()
self.button_container.raise_()
self.uninstall_container.raise_() # 添加新按钮到层级顺序
self.exit_container.raise_()
self.menu_area.raise_() # 确保菜单区域在背景之上
# self.menubar.raise_() # 不再需要菜单栏
self.settings_btn.raise_() # 确保设置按钮在上层
self.help_btn.raise_() # 确保帮助按钮在上层
self.title_bar.raise_() # 确保标题栏在最上层
self.retranslateUi(MainWindows)
QMetaObject.connectSlotsByName(MainWindows)
# setupUi
def retranslateUi(self, MainWindows):
MainWindows.setWindowTitle(QCoreApplication.translate("MainWindows", u" UI Test", None))
MainWindows.setWindowTitle(QCoreApplication.translate("MainWindows", f"{APP_NAME} v{APP_VERSION}", None))
self.loadbg.setText("")
self.vol1bg.setText("")
self.vol2bg.setText("")
@@ -146,10 +493,18 @@ class Ui_MainWindows(object):
#if QT_CONFIG(accessibility)
self.start_install_btn.setAccessibleDescription("")
#endif // QT_CONFIG(accessibility)
self.start_install_btn.setText("")
self.exit_btn.setText("")
self.menubg.setText("")
self.menu.setTitle(QCoreApplication.translate("MainWindows", u"\u8bbe\u7f6e", None))
self.menu_2.setTitle(QCoreApplication.translate("MainWindows", u"\u5e2e\u52a9", None))
self.menu.setTitle(QCoreApplication.translate("MainWindows", u"设置", None))
self.menu_2.setTitle(QCoreApplication.translate("MainWindows", u"帮助", None))
# retranslateUi
def show_menu(self, menu, button):
"""显示菜单
Args:
menu: 要显示的菜单
button: 触发菜单的按钮
"""
# 计算菜单显示位置
pos = button.mapToGlobal(button.rect().bottomLeft())
menu.exec(pos)

View File

@@ -1,333 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindows</class>
<widget class="QMainWindow" name="MainWindows">
<property name="enabled">
<bool>true</bool>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1024</width>
<height>576</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>1024</width>
<height>576</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>1024</width>
<height>576</height>
</size>
</property>
<property name="mouseTracking">
<bool>false</bool>
</property>
<property name="tabletTracking">
<bool>false</bool>
</property>
<property name="acceptDrops">
<bool>true</bool>
</property>
<property name="windowTitle">
<string> UI Test</string>
</property>
<property name="autoFillBackground">
<bool>true</bool>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonStyle::ToolButtonIconOnly</enum>
</property>
<property name="animated">
<bool>true</bool>
</property>
<property name="documentMode">
<bool>false</bool>
</property>
<property name="dockNestingEnabled">
<bool>false</bool>
</property>
<widget class="QWidget" name="centralwidget">
<property name="autoFillBackground">
<bool>true</bool>
</property>
<widget class="QLabel" name="loadbg">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1031</width>
<height>561</height>
</rect>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap>IMG/BG/bg2.jpg</pixmap>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
<widget class="QLabel" name="vol1bg">
<property name="geometry">
<rect>
<x>0</x>
<y>120</y>
<width>93</width>
<height>64</height>
</rect>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap>IMG/LOGO/vo01_logo.png</pixmap>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
<widget class="QLabel" name="vol2bg">
<property name="geometry">
<rect>
<x>0</x>
<y>180</y>
<width>93</width>
<height>64</height>
</rect>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap>IMG/LOGO/vo02_logo.png</pixmap>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
<widget class="QLabel" name="vol3bg">
<property name="geometry">
<rect>
<x>0</x>
<y>240</y>
<width>93</width>
<height>64</height>
</rect>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap>IMG/LOGO/vo03_logo.png</pixmap>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
<widget class="QLabel" name="vol4bg">
<property name="geometry">
<rect>
<x>0</x>
<y>300</y>
<width>93</width>
<height>64</height>
</rect>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap>IMG/LOGO/vo04_logo.png</pixmap>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
<widget class="QLabel" name="afterbg">
<property name="geometry">
<rect>
<x>0</x>
<y>360</y>
<width>93</width>
<height>64</height>
</rect>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap>IMG/LOGO/voaf_logo.png</pixmap>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
<widget class="QLabel" name="Mainbg">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1031</width>
<height>561</height>
</rect>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap>IMG/BG/bg3.jpg</pixmap>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
<widget class="QPushButton" name="start_install_btn">
<property name="enabled">
<bool>true</bool>
</property>
<property name="geometry">
<rect>
<x>780</x>
<y>250</y>
<width>191</width>
<height>91</height>
</rect>
</property>
<property name="accessibleDescription">
<string/>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset>
<normaloff>IMG/BTN/start_install.bmp</normaloff>IMG/BTN/start_install.bmp</iconset>
</property>
<property name="iconSize">
<size>
<width>189</width>
<height>110</height>
</size>
</property>
<property name="checkable">
<bool>false</bool>
</property>
<property name="autoRepeat">
<bool>false</bool>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
<widget class="QPushButton" name="exit_btn">
<property name="enabled">
<bool>true</bool>
</property>
<property name="geometry">
<rect>
<x>780</x>
<y>340</y>
<width>191</width>
<height>91</height>
</rect>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset>
<normaloff>IMG/BTN/exit.bmp</normaloff>IMG/BTN/exit.bmp</iconset>
</property>
<property name="iconSize">
<size>
<width>189</width>
<height>110</height>
</size>
</property>
<property name="checkable">
<bool>false</bool>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
<widget class="QLabel" name="menubg">
<property name="geometry">
<rect>
<x>710</x>
<y>0</y>
<width>321</width>
<height>561</height>
</rect>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap>IMG/BG/menubg.jpg</pixmap>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
<zorder>loadbg</zorder>
<zorder>vol1bg</zorder>
<zorder>vol2bg</zorder>
<zorder>vol3bg</zorder>
<zorder>vol4bg</zorder>
<zorder>afterbg</zorder>
<zorder>Mainbg</zorder>
<zorder>menubg</zorder>
<zorder>start_install_btn</zorder>
<zorder>exit_btn</zorder>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1024</width>
<height>21</height>
</rect>
</property>
<widget class="QMenu" name="menu">
<property name="title">
<string>设置</string>
</property>
<addaction name="separator"/>
<addaction name="action_2"/>
</widget>
<widget class="QMenu" name="menu_2">
<property name="title">
<string>关于</string>
</property>
</widget>
<addaction name="menu"/>
<addaction name="menu_2"/>
</widget>
<action name="action_2">
<property name="text">
<string>update - sd</string>
</property>
</action>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>480</width>
<height>270</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -1,12 +1,14 @@
from .logger import Logger
from .helpers import (
load_base64_image, HashManager, AdminPrivileges, msgbox_frame,
load_config, save_config, HostsManager, censor_url, resource_path
load_config, save_config, HostsManager, censor_url, resource_path,
load_image_from_file
)
__all__ = [
'Logger',
'load_base64_image',
'load_image_from_file',
'HashManager',
'AdminPrivileges',
'msgbox_frame',

View File

@@ -9,7 +9,6 @@ import psutil
from PySide6 import QtCore, QtWidgets
import re
from PySide6.QtGui import QIcon, QPixmap
from data.pic_data import img_data
from data.config import APP_NAME, CONFIG_FILE
def resource_path(relative_path):
@@ -37,14 +36,28 @@ def load_base64_image(base64_str):
pixmap.loadFromData(base64.b64decode(base64_str))
return pixmap
def load_image_from_file(file_path):
"""加载图像文件到QPixmap
Args:
file_path: 图像文件路径
Returns:
QPixmap: 加载的图像
"""
if os.path.exists(file_path):
return QPixmap(file_path)
return QPixmap()
def msgbox_frame(title, text, buttons=QtWidgets.QMessageBox.StandardButton.NoButton):
msg_box = QtWidgets.QMessageBox()
msg_box.setWindowTitle(title)
msg_box.setWindowModality(QtCore.Qt.WindowModality.WindowModal)
icon_data = img_data.get("icon")
if icon_data:
pixmap = load_base64_image(icon_data)
# 直接加载图标文件
icon_path = resource_path(os.path.join("IMG", "ICO", "icon.png"))
if os.path.exists(icon_path):
pixmap = QPixmap(icon_path)
if not pixmap.isNull():
msg_box.setWindowIcon(QIcon(pixmap))
msg_box.setIconPixmap(pixmap.scaled(64, 64, QtCore.Qt.AspectRatioMode.KeepAspectRatio, QtCore.Qt.TransformationMode.SmoothTransformation))
@@ -99,8 +112,25 @@ class HashManager:
print(f"Error calculating hash for {file_path}: {e}")
return results
def hash_pop_window(self):
msg_box = msgbox_frame(f"通知 - {APP_NAME}", "\n正在检验文件状态...\n")
def hash_pop_window(self, check_type="default"):
"""显示文件检验窗口
Args:
check_type: 检查类型,可以是 'pre'(预检查), 'after'(后检查), 'extraction'(解压后检查)
Returns:
QMessageBox: 消息框实例
"""
message = "\n正在检验文件状态...\n"
if check_type == "pre":
message = "\n正在检查游戏文件以确定需要安装的补丁...\n"
elif check_type == "after":
message = "\n正在检验本地文件完整性...\n"
elif check_type == "extraction":
message = "\n正在验证下载的解压文件完整性...\n"
msg_box = msgbox_frame(f"通知 - {APP_NAME}", message)
msg_box.open()
QtWidgets.QApplication.processEvents()
return msg_box
@@ -158,6 +188,7 @@ class AdminPrivileges:
"nekopara_vol1.exe",
"nekopara_vol2.exe",
"NEKOPARAvol3.exe",
"NEKOPARAvol3.exe.nocrack",
"nekopara_vol4.exe",
"nekopara_after.exe",
]
@@ -200,33 +231,40 @@ class AdminPrivileges:
def check_and_terminate_processes(self):
for proc in psutil.process_iter(["pid", "name"]):
if proc.info["name"] in self.required_exes:
msg_box = msgbox_frame(
f"进程检测 - {APP_NAME}",
f"\n检测到游戏正在运行: {proc.info['name']} \n\n是否终止?\n",
QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No,
)
reply = msg_box.exec()
if reply == QtWidgets.QMessageBox.StandardButton.Yes:
try:
proc.terminate()
proc.wait(timeout=3)
except psutil.AccessDenied:
proc_name = proc.info["name"].lower() if proc.info["name"] else ""
# 检查进程名是否匹配任何需要终止的游戏进程
for exe in self.required_exes:
if exe.lower() == proc_name:
# 获取不带.nocrack的游戏名称用于显示
display_name = exe.replace(".nocrack", "")
msg_box = msgbox_frame(
f"进程检测 - {APP_NAME}",
f"\n检测到游戏正在运行: {display_name} \n\n是否终止?\n",
QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No,
)
reply = msg_box.exec()
if reply == QtWidgets.QMessageBox.StandardButton.Yes:
try:
proc.terminate()
proc.wait(timeout=3)
except psutil.AccessDenied:
msg_box = msgbox_frame(
f"错误 - {APP_NAME}",
f"\n无法关闭游戏: {display_name} \n\n请手动关闭后重启应用\n",
QtWidgets.QMessageBox.StandardButton.Ok,
)
msg_box.exec()
sys.exit(1)
else:
msg_box = msgbox_frame(
f"错误 - {APP_NAME}",
f"\n无法关闭游戏: {proc.info['name']} \n\n请手动关闭后重启应用\n",
f"进程检测 - {APP_NAME}",
f"\n关闭游戏: {display_name} \n\n请手动关闭后重启应用\n",
QtWidgets.QMessageBox.StandardButton.Ok,
)
msg_box.exec()
sys.exit(1)
else:
msg_box = msgbox_frame(
f"进程检测 - {APP_NAME}",
f"\n未关闭的游戏: {proc.info['name']} \n\n请手动关闭后重启应用\n",
QtWidgets.QMessageBox.StandardButton.Ok,
)
msg_box.exec()
sys.exit(1)
class HostsManager:
def __init__(self):