535 lines
16 KiB
Python
535 lines
16 KiB
Python
# <AUTO_MAA:A MAA Multi Account Management and Automation Tool>
|
||
# Copyright © <2024> <DLmaster361>
|
||
|
||
# This file is part of AUTO_MAA.
|
||
|
||
# AUTO_MAA is free software: you can redistribute it and/or modify
|
||
# it under the terms of the GNU General Public License as published
|
||
# by the Free Software Foundation, either version 3 of the License,
|
||
# or (at your option) any later version.
|
||
|
||
# AUTO_MAA is distributed in the hope that it will be useful,
|
||
# but WITHOUT ANY WARRANTY; without even the implied warranty
|
||
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
||
# the GNU General Public License for more details.
|
||
|
||
# You should have received a copy of the GNU General Public License
|
||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||
|
||
# DLmaster_361@163.com
|
||
|
||
"""
|
||
AUTO_MAA
|
||
AUTO_MAA组件
|
||
v4.2
|
||
作者:DLmaster_361
|
||
"""
|
||
|
||
from PySide6.QtWidgets import QWidget, QHBoxLayout
|
||
from PySide6.QtCore import Qt, QTime, QEvent
|
||
from PySide6.QtGui import QIcon, QPixmap, QPainter, QPainterPath
|
||
from qfluentwidgets import (
|
||
LineEdit,
|
||
PasswordLineEdit,
|
||
MessageBoxBase,
|
||
SubtitleLabel,
|
||
SettingCard,
|
||
SpinBox,
|
||
FluentIconBase,
|
||
Signal,
|
||
ComboBox,
|
||
CheckBox,
|
||
IconWidget,
|
||
FluentIcon,
|
||
CardWidget,
|
||
BodyLabel,
|
||
qconfig,
|
||
ConfigItem,
|
||
TimeEdit,
|
||
OptionsConfigItem,
|
||
TeachingTip,
|
||
TransparentToolButton,
|
||
TeachingTipTailPosition,
|
||
)
|
||
from qfluentwidgets.common.overload import singledispatchmethod
|
||
import os
|
||
from typing import Optional, Union, List
|
||
|
||
from app.services import Crypto
|
||
|
||
|
||
class LineEditMessageBox(MessageBoxBase):
|
||
"""输入对话框"""
|
||
|
||
def __init__(self, parent, title: str, content: str, mode: str):
|
||
super().__init__(parent)
|
||
self.title = SubtitleLabel(title)
|
||
|
||
if mode == "明文":
|
||
self.input = LineEdit()
|
||
self.input.setClearButtonEnabled(True)
|
||
elif mode == "密码":
|
||
self.input = PasswordLineEdit()
|
||
|
||
self.input.setPlaceholderText(content)
|
||
|
||
# 将组件添加到布局中
|
||
self.viewLayout.addWidget(self.title)
|
||
self.viewLayout.addWidget(self.input)
|
||
|
||
|
||
class ComboBoxMessageBox(MessageBoxBase):
|
||
"""选择对话框"""
|
||
|
||
def __init__(self, parent, title: str, content: List[str], list: List[List[str]]):
|
||
super().__init__(parent)
|
||
self.title = SubtitleLabel(title)
|
||
|
||
Widget = QWidget()
|
||
Layout = QHBoxLayout(Widget)
|
||
|
||
self.input: List[ComboBox] = []
|
||
|
||
for i in range(len(content)):
|
||
|
||
self.input.append(ComboBox())
|
||
self.input[i].addItems(list[i])
|
||
self.input[i].setCurrentIndex(-1)
|
||
self.input[i].setPlaceholderText(content[i])
|
||
Layout.addWidget(self.input[i])
|
||
|
||
# 将组件添加到布局中
|
||
self.viewLayout.addWidget(self.title)
|
||
self.viewLayout.addWidget(Widget)
|
||
|
||
|
||
class LineEditSettingCard(SettingCard):
|
||
"""Setting card with LineEdit"""
|
||
|
||
textChanged = Signal(str)
|
||
|
||
def __init__(
|
||
self,
|
||
text,
|
||
icon: Union[str, QIcon, FluentIconBase],
|
||
title,
|
||
content=None,
|
||
configItem: ConfigItem = None,
|
||
parent=None,
|
||
):
|
||
|
||
super().__init__(icon, title, content, parent)
|
||
self.configItem = configItem
|
||
self.LineEdit = LineEdit(self)
|
||
self.LineEdit.setMinimumWidth(250)
|
||
self.LineEdit.setPlaceholderText(text)
|
||
|
||
if configItem:
|
||
self.setValue(qconfig.get(configItem))
|
||
configItem.valueChanged.connect(self.setValue)
|
||
|
||
self.hBoxLayout.addWidget(self.LineEdit, 0, Qt.AlignRight)
|
||
self.hBoxLayout.addSpacing(16)
|
||
|
||
self.LineEdit.textChanged.connect(self.__textChanged)
|
||
|
||
def __textChanged(self, content: str):
|
||
self.setValue(content)
|
||
self.textChanged.emit(content)
|
||
|
||
def setValue(self, content: str):
|
||
if self.configItem:
|
||
qconfig.set(self.configItem, content)
|
||
|
||
self.LineEdit.setText(content)
|
||
|
||
|
||
class PasswordLineEditSettingCard(SettingCard):
|
||
"""Setting card with PasswordLineEdit"""
|
||
|
||
textChanged = Signal(str)
|
||
|
||
def __init__(
|
||
self,
|
||
text,
|
||
icon: Union[str, QIcon, FluentIconBase],
|
||
title,
|
||
content=None,
|
||
configItem: ConfigItem = None,
|
||
parent=None,
|
||
):
|
||
|
||
super().__init__(icon, title, content, parent)
|
||
self.configItem = configItem
|
||
self.LineEdit = PasswordLineEdit(self)
|
||
self.LineEdit.setMinimumWidth(250)
|
||
self.LineEdit.setPlaceholderText(text)
|
||
|
||
if configItem:
|
||
self.setValue(qconfig.get(configItem))
|
||
configItem.valueChanged.connect(self.setValue)
|
||
|
||
self.hBoxLayout.addWidget(self.LineEdit, 0, Qt.AlignRight)
|
||
self.hBoxLayout.addSpacing(16)
|
||
|
||
self.LineEdit.textChanged.connect(self.__textChanged)
|
||
|
||
def __textChanged(self, content: str):
|
||
self.setValue(Crypto.win_encryptor(content))
|
||
self.textChanged.emit(content)
|
||
|
||
def setValue(self, content: str):
|
||
if self.configItem:
|
||
qconfig.set(self.configItem, content)
|
||
|
||
self.LineEdit.setText(Crypto.win_decryptor(content))
|
||
|
||
|
||
class SpinBoxSettingCard(SettingCard):
|
||
"""Setting card with SpinBox"""
|
||
|
||
textChanged = Signal(int)
|
||
|
||
def __init__(
|
||
self,
|
||
range: tuple[int, int],
|
||
icon: Union[str, QIcon, FluentIconBase],
|
||
title,
|
||
content=None,
|
||
configItem: ConfigItem = None,
|
||
parent=None,
|
||
):
|
||
|
||
super().__init__(icon, title, content, parent)
|
||
self.configItem = configItem
|
||
self.SpinBox = SpinBox(self)
|
||
self.SpinBox.setRange(range[0], range[1])
|
||
self.SpinBox.setMinimumWidth(150)
|
||
|
||
if configItem:
|
||
self.setValue(qconfig.get(configItem))
|
||
configItem.valueChanged.connect(self.setValue)
|
||
|
||
self.hBoxLayout.addWidget(self.SpinBox, 0, Qt.AlignRight)
|
||
self.hBoxLayout.addSpacing(16)
|
||
|
||
self.SpinBox.valueChanged.connect(self.__valueChanged)
|
||
|
||
def __valueChanged(self, value: int):
|
||
self.setValue(value)
|
||
self.textChanged.emit(value)
|
||
|
||
def setValue(self, value: int):
|
||
if self.configItem:
|
||
qconfig.set(self.configItem, value)
|
||
|
||
self.SpinBox.setValue(value)
|
||
|
||
|
||
class NoOptionComboBoxSettingCard(SettingCard):
|
||
|
||
def __init__(
|
||
self,
|
||
configItem: OptionsConfigItem,
|
||
icon: Union[str, QIcon, FluentIconBase],
|
||
title,
|
||
content=None,
|
||
value=None,
|
||
texts=None,
|
||
parent=None,
|
||
):
|
||
|
||
super().__init__(icon, title, content, parent)
|
||
self.configItem = configItem
|
||
self.comboBox = ComboBox(self)
|
||
self.comboBox.setMinimumWidth(250)
|
||
self.hBoxLayout.addWidget(self.comboBox, 0, Qt.AlignRight)
|
||
self.hBoxLayout.addSpacing(16)
|
||
|
||
self.optionToText = {o: t for o, t in zip(value, texts)}
|
||
for text, option in zip(texts, value):
|
||
self.comboBox.addItem(text, userData=option)
|
||
|
||
self.comboBox.setCurrentText(self.optionToText[qconfig.get(configItem)])
|
||
self.comboBox.currentIndexChanged.connect(self._onCurrentIndexChanged)
|
||
configItem.valueChanged.connect(self.setValue)
|
||
|
||
def _onCurrentIndexChanged(self, index: int):
|
||
|
||
qconfig.set(self.configItem, self.comboBox.itemData(index))
|
||
|
||
def setValue(self, value):
|
||
if value not in self.optionToText:
|
||
return
|
||
|
||
self.comboBox.setCurrentText(self.optionToText[value])
|
||
qconfig.set(self.configItem, value)
|
||
|
||
|
||
class TimeEditSettingCard(SettingCard):
|
||
|
||
enabledChanged = Signal(bool)
|
||
timeChanged = Signal(str)
|
||
|
||
def __init__(
|
||
self,
|
||
icon: Union[str, QIcon, FluentIconBase],
|
||
title,
|
||
content=None,
|
||
configItem_bool: ConfigItem = None,
|
||
configItem_time: ConfigItem = None,
|
||
parent=None,
|
||
):
|
||
|
||
super().__init__(icon, title, content, parent)
|
||
self.configItem_bool = configItem_bool
|
||
self.configItem_time = configItem_time
|
||
self.CheckBox = CheckBox(self)
|
||
self.CheckBox.setTristate(False)
|
||
self.TimeEdit = TimeEdit(self)
|
||
self.TimeEdit.setDisplayFormat("HH:mm")
|
||
self.TimeEdit.setMinimumWidth(150)
|
||
|
||
if configItem_bool:
|
||
self.setValue_bool(qconfig.get(configItem_bool))
|
||
configItem_bool.valueChanged.connect(self.setValue_bool)
|
||
|
||
if configItem_time:
|
||
self.setValue_time(qconfig.get(configItem_time))
|
||
configItem_time.valueChanged.connect(self.setValue_time)
|
||
|
||
self.hBoxLayout.addWidget(self.CheckBox, 0, Qt.AlignRight)
|
||
self.hBoxLayout.addWidget(self.TimeEdit, 0, Qt.AlignRight)
|
||
self.hBoxLayout.addSpacing(16)
|
||
|
||
self.CheckBox.stateChanged.connect(self.__enableChanged)
|
||
self.TimeEdit.timeChanged.connect(self.__timeChanged)
|
||
|
||
def __timeChanged(self, value: QTime):
|
||
self.setValue_time(value.toString("HH:mm"))
|
||
self.timeChanged.emit(value.toString("HH:mm"))
|
||
|
||
def __enableChanged(self, value: int):
|
||
if value == 0:
|
||
self.setValue_bool(False)
|
||
self.enabledChanged.emit(False)
|
||
else:
|
||
self.setValue_bool(True)
|
||
self.enabledChanged.emit(True)
|
||
|
||
def setValue_bool(self, value: bool):
|
||
if self.configItem_bool:
|
||
qconfig.set(self.configItem_bool, value)
|
||
|
||
self.CheckBox.setChecked(value)
|
||
|
||
def setValue_time(self, value: str):
|
||
if self.configItem_time:
|
||
qconfig.set(self.configItem_time, value)
|
||
|
||
self.TimeEdit.setTime(QTime.fromString(value, "HH:mm"))
|
||
|
||
|
||
class StatefulItemCard(CardWidget):
|
||
|
||
def __init__(self, item: list, parent=None):
|
||
super().__init__(parent)
|
||
|
||
self.Layout = QHBoxLayout(self)
|
||
|
||
self.Label = BodyLabel(item[0], self)
|
||
self.icon = IconWidget(FluentIcon.MORE, self)
|
||
self.icon.setFixedSize(16, 16)
|
||
self.update_status(item[1])
|
||
|
||
self.Layout.addWidget(self.icon)
|
||
self.Layout.addWidget(self.Label)
|
||
self.Layout.addStretch(1)
|
||
|
||
def update_status(self, status: str):
|
||
|
||
if status == "完成":
|
||
self.icon.setIcon(FluentIcon.ACCEPT)
|
||
self.Label.setTextColor("#0eb840", "#0eb840")
|
||
elif status == "等待":
|
||
self.icon.setIcon(FluentIcon.MORE)
|
||
self.Label.setTextColor("#161823", "#e3f9fd")
|
||
elif status == "运行":
|
||
self.icon.setIcon(FluentIcon.PLAY)
|
||
self.Label.setTextColor("#177cb0", "#70f3ff")
|
||
elif status == "跳过":
|
||
self.icon.setIcon(FluentIcon.REMOVE)
|
||
self.Label.setTextColor("#75878a", "#7397ab")
|
||
elif status == "异常":
|
||
self.icon.setIcon(FluentIcon.CLOSE)
|
||
self.Label.setTextColor("#ff2121", "#ff2121")
|
||
|
||
|
||
class QuantifiedItemCard(CardWidget):
|
||
|
||
def __init__(self, item: list, parent=None):
|
||
super().__init__(parent)
|
||
|
||
self.Layout = QHBoxLayout(self)
|
||
|
||
self.Name = BodyLabel(item[0], self)
|
||
self.Numb = BodyLabel(str(item[1]), self)
|
||
|
||
self.Layout.addWidget(self.Name)
|
||
self.Layout.addStretch(1)
|
||
self.Layout.addWidget(self.Numb)
|
||
|
||
|
||
class IconButton(TransparentToolButton):
|
||
"""包含下拉框的自定义设置卡片类。"""
|
||
|
||
@singledispatchmethod
|
||
def __init__(self, parent: QWidget = None):
|
||
TransparentToolButton.__init__(self, parent)
|
||
|
||
self._tooltip: Optional[TeachingTip] = None
|
||
|
||
@__init__.register
|
||
def _(self, icon: Union[str, QIcon, FluentIconBase], parent: QWidget = None):
|
||
self.__init__(parent)
|
||
self.setIcon(icon)
|
||
|
||
@__init__.register
|
||
def _(
|
||
self,
|
||
icon: Union[str, QIcon, FluentIconBase],
|
||
isTooltip: bool,
|
||
tip_title: str,
|
||
tip_content: str,
|
||
parent: QWidget = None,
|
||
):
|
||
self.__init__(parent)
|
||
self.setIcon(icon)
|
||
|
||
# 处理工具提示
|
||
if isTooltip:
|
||
self.installEventFilter(self)
|
||
|
||
self.tip_title: str = tip_title
|
||
self.tip_content: str = tip_content
|
||
|
||
def eventFilter(self, obj, event: QEvent) -> bool:
|
||
"""处理鼠标事件。"""
|
||
if event.type() == QEvent.Type.Enter:
|
||
self._show_tooltip()
|
||
elif event.type() == QEvent.Type.Leave:
|
||
self._hide_tooltip()
|
||
return super().eventFilter(obj, event)
|
||
|
||
def _show_tooltip(self) -> None:
|
||
"""显示工具提示。"""
|
||
self._tooltip = TeachingTip.create(
|
||
target=self,
|
||
title=self.tip_title,
|
||
content=self.tip_content,
|
||
tailPosition=TeachingTipTailPosition.RIGHT,
|
||
isClosable=False,
|
||
duration=-1,
|
||
parent=self,
|
||
)
|
||
# 设置偏移
|
||
if self._tooltip:
|
||
tooltip_pos = self.mapToGlobal(self.rect().topRight())
|
||
|
||
tooltip_pos.setX(
|
||
tooltip_pos.x() - self._tooltip.size().width() - 40
|
||
) # 水平偏移
|
||
tooltip_pos.setY(
|
||
tooltip_pos.y() - self._tooltip.size().height() / 2 + 35
|
||
) # 垂直偏移
|
||
|
||
self._tooltip.move(tooltip_pos)
|
||
|
||
def _hide_tooltip(self) -> None:
|
||
"""隐藏工具提示。"""
|
||
if self._tooltip:
|
||
self._tooltip.close()
|
||
self._tooltip = None
|
||
|
||
def __hash__(self):
|
||
return id(self)
|
||
|
||
def __eq__(self, other):
|
||
if not isinstance(other, self.__class__):
|
||
return NotImplemented
|
||
return self is other
|
||
|
||
|
||
class Banner(QWidget):
|
||
"""展示带有圆角的固定大小横幅小部件"""
|
||
|
||
def __init__(self, image_path: str = None, parent=None):
|
||
QWidget.__init__(self, parent)
|
||
self.image_path = None
|
||
self.banner_image = None
|
||
self.scaled_image = None
|
||
|
||
if image_path:
|
||
self.set_banner_image(image_path)
|
||
|
||
def set_banner_image(self, image_path: str):
|
||
"""设置横幅图片"""
|
||
self.image_path = image_path
|
||
self.banner_image = self.load_banner_image(image_path)
|
||
self.update_scaled_image()
|
||
|
||
def load_banner_image(self, image_path: str) -> QPixmap:
|
||
"""加载横幅图片,或创建渐变备用图片"""
|
||
if os.path.isfile(image_path):
|
||
return QPixmap(image_path)
|
||
return self._create_fallback_image()
|
||
|
||
def _create_fallback_image(self):
|
||
"""创建渐变备用图片"""
|
||
fallback_image = QPixmap(2560, 1280) # 使用原始图片的大小
|
||
fallback_image.fill(Qt.GlobalColor.gray)
|
||
return fallback_image
|
||
|
||
def update_scaled_image(self):
|
||
"""按高度缩放图片,宽度保持比例,超出裁剪"""
|
||
if self.banner_image:
|
||
self.scaled_image = self.banner_image.scaled(
|
||
self.size(),
|
||
Qt.AspectRatioMode.KeepAspectRatioByExpanding,
|
||
Qt.TransformationMode.SmoothTransformation,
|
||
)
|
||
self.update()
|
||
|
||
def paintEvent(self, event):
|
||
"""重载 paintEvent 以绘制缩放后的图片"""
|
||
if self.scaled_image:
|
||
painter = QPainter(self)
|
||
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
|
||
painter.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform)
|
||
|
||
# 创建圆角路径
|
||
path = QPainterPath()
|
||
path.addRoundedRect(self.rect(), 20, 20)
|
||
painter.setClipPath(path)
|
||
|
||
# 计算绘制位置,使图片居中
|
||
x = (self.width() - self.scaled_image.width()) // 2
|
||
y = (self.height() - self.scaled_image.height()) // 2
|
||
|
||
# 绘制缩放后的图片
|
||
painter.drawPixmap(x, y, self.scaled_image)
|
||
|
||
def resizeEvent(self, event):
|
||
"""重载 resizeEvent 以更新缩放后的图片"""
|
||
self.update_scaled_image()
|
||
QWidget.resizeEvent(self, event)
|
||
|
||
def set_percentage_size(self, width_percentage, height_percentage):
|
||
"""设置 Banner 的大小为窗口大小的百分比"""
|
||
parent = self.parentWidget()
|
||
if parent:
|
||
new_width = int(parent.width() * width_percentage)
|
||
new_height = int(parent.height() * height_percentage)
|
||
self.setFixedSize(new_width, new_height)
|
||
self.update_scaled_image()
|