mirror of
https://github.com/rbreu/beeref.git
synced 2026-03-11 08:54:28 +00:00
在settings.py上加注释
This commit is contained in:
parent
e82a220222
commit
617f9da267
1 changed files with 215 additions and 48 deletions
|
|
@ -13,224 +13,391 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with BeeRef. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
from functools import partial
|
||||
import logging
|
||||
# 导入工具函数和库
|
||||
from functools import partial # 用于固定函数参数,创建偏函数
|
||||
import logging # 用于日志记录
|
||||
|
||||
from PyQt6 import QtWidgets
|
||||
from PyQt6.QtCore import Qt
|
||||
# 导入PyQt6 UI组件和核心常量
|
||||
from PyQt6 import QtWidgets # 导入QtWidgets模块,用于创建UI组件
|
||||
from PyQt6.QtCore import Qt # 导入Qt核心常量(如复选框状态)
|
||||
|
||||
from beeref import constants
|
||||
from beeref.config import BeeSettings, settings_events
|
||||
# 导入应用内部模块
|
||||
from beeref import constants # 包含应用名称、变更符号等常量
|
||||
from beeref.config import BeeSettings, settings_events # 导入设置管理类和事件信号
|
||||
|
||||
|
||||
# 初始化日志记录器,命名为当前模块名
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GroupBase(QtWidgets.QGroupBox):
|
||||
TITLE = None
|
||||
HELPTEXT = None
|
||||
KEY = None
|
||||
"""设置项分组的基类,继承自QGroupBox(UI分组容器)
|
||||
|
||||
类属性(需子类重写):
|
||||
- TITLE: 分组标题
|
||||
- HELPTEXT: 分组帮助文本(可选)
|
||||
- KEY: 关联的配置键(对应BeeSettings中的FIELDS键)
|
||||
"""
|
||||
|
||||
TITLE = None # 分组标题(子类必须重写)
|
||||
HELPTEXT = None # 分组帮助文本(子类可重写,可选)
|
||||
KEY = None # 关联的配置键(子类必须重写)
|
||||
|
||||
def __init__(self):
|
||||
"""初始化基类,创建分组容器并关联配置"""
|
||||
# 调用父类QGroupBox的构造函数
|
||||
super().__init__()
|
||||
# 实例化配置管理对象,用于读取/写入配置
|
||||
self.settings = BeeSettings()
|
||||
# 更新分组标题(含变更标记)
|
||||
self.update_title()
|
||||
# 创建垂直布局,用于排列分组内的UI组件
|
||||
self.layout = QtWidgets.QVBoxLayout()
|
||||
# 将布局应用到当前分组容器
|
||||
self.setLayout(self.layout)
|
||||
# 连接“恢复默认设置”信号到处理函数
|
||||
settings_events.restore_defaults.connect(self.on_restore_defaults)
|
||||
|
||||
# 如果有帮助文本,创建标签并添加到布局
|
||||
if self.HELPTEXT:
|
||||
# 创建帮助文本标签
|
||||
helptxt = QtWidgets.QLabel(self.HELPTEXT)
|
||||
# 允许帮助文本自动换行
|
||||
helptxt.setWordWrap(True)
|
||||
# 将帮助标签添加到布局
|
||||
self.layout.addWidget(helptxt)
|
||||
|
||||
def update_title(self):
|
||||
"""更新分组标题,若配置已修改则添加变更符号"""
|
||||
# 初始化标题列表(基础标题)
|
||||
title = [self.TITLE]
|
||||
# 检查当前配置是否偏离默认值
|
||||
if self.settings.value_changed(self.KEY):
|
||||
# 若已修改,添加变更符号(如星号,定义在constants中)
|
||||
title.append(constants.CHANGED_SYMBOL)
|
||||
# 设置最终标题(拼接基础标题和变更符号)
|
||||
self.setTitle(' '.join(title))
|
||||
|
||||
def on_value_changed(self, value):
|
||||
"""UI值变更的处理函数,将新值写入配置
|
||||
|
||||
参数:
|
||||
value: UI组件传递的原始值
|
||||
"""
|
||||
# 若标记为“忽略值变更”,直接返回(避免递归触发)
|
||||
if self.ignore_value_changed:
|
||||
return
|
||||
|
||||
# 将UI原始值转换为配置所需格式(子类可重写转换逻辑)
|
||||
value = self.convert_value_from_qt(value)
|
||||
# 检查转换后的值是否与当前配置值不同
|
||||
if value != self.settings.valueOrDefault(self.KEY):
|
||||
# 记录配置变更日志
|
||||
logger.debug(f'Setting {self.KEY} changed to: {value}')
|
||||
# 将新值写入配置
|
||||
self.settings.setValue(self.KEY, value)
|
||||
# 更新分组标题(添加/移除变更符号)
|
||||
self.update_title()
|
||||
|
||||
def convert_value_from_qt(self, value):
|
||||
"""将UI组件的原始值转换为配置格式(默认无转换)
|
||||
|
||||
参数:
|
||||
value: UI组件传递的原始值
|
||||
|
||||
返回:
|
||||
转换后的值(默认返回原始值)
|
||||
"""
|
||||
return value
|
||||
|
||||
def on_restore_defaults(self):
|
||||
"""恢复当前配置项为默认值,并更新UI"""
|
||||
# 获取当前配置项的默认值
|
||||
new_value = self.settings.valueOrDefault(self.KEY)
|
||||
# 标记为“忽略值变更”,避免UI更新触发配置写入
|
||||
self.ignore_value_changed = True
|
||||
# 调用子类实现的set_value方法,更新UI组件显示
|
||||
self.set_value(new_value)
|
||||
# 取消“忽略值变更”标记
|
||||
self.ignore_value_changed = False
|
||||
# 更新分组标题(移除变更符号)
|
||||
self.update_title()
|
||||
|
||||
|
||||
class RadioGroup(GroupBase):
|
||||
OPTIONS = None
|
||||
"""单选按钮分组类,继承自GroupBase,用于多选项配置"""
|
||||
|
||||
OPTIONS = None # 单选按钮选项列表(子类必须重写)
|
||||
# OPTIONS格式:[(配置值1, 按钮文本1, 按钮提示1), (配置值2, 按钮文本2, 按钮提示2), ...]
|
||||
|
||||
def __init__(self):
|
||||
"""初始化单选按钮分组,创建单选按钮并关联配置"""
|
||||
# 调用父类GroupBase的构造函数
|
||||
super().__init__()
|
||||
|
||||
# 标记为“忽略值变更”,避免初始化时触发配置写入
|
||||
self.ignore_value_changed = True
|
||||
# 存储单选按钮的字典:key=配置值,value=QRadioButton对象
|
||||
self.buttons = {}
|
||||
|
||||
# 遍历选项列表,创建每个单选按钮
|
||||
for (value, label, helptext) in self.OPTIONS:
|
||||
# 创建单选按钮,设置显示文本
|
||||
btn = QtWidgets.QRadioButton(label)
|
||||
# 将按钮存入字典,关联配置值
|
||||
self.buttons[value] = btn
|
||||
# 设置按钮的悬停提示文本
|
||||
btn.setToolTip(helptext)
|
||||
# 连接按钮“状态切换”信号到值变更处理函数(固定当前选项的配置值)
|
||||
btn.toggled.connect(partial(self.on_value_changed, value=value))
|
||||
# 检查当前选项是否为配置的默认值,若是则选中该按钮
|
||||
if value == self.settings.valueOrDefault(self.KEY):
|
||||
btn.setChecked(True)
|
||||
# 将按钮添加到分组布局
|
||||
self.layout.addWidget(btn)
|
||||
|
||||
# 取消“忽略值变更”标记,允许后续用户操作触发配置写入
|
||||
self.ignore_value_changed = False
|
||||
# 添加弹性拉伸,将按钮顶到布局上方(避免按钮分散)
|
||||
self.layout.addStretch(100)
|
||||
|
||||
def set_value(self, value):
|
||||
"""根据配置值更新单选按钮的选中状态
|
||||
|
||||
参数:
|
||||
value: 要设置的配置值
|
||||
"""
|
||||
# 遍历所有单选按钮,匹配配置值并选中对应按钮
|
||||
for old_value, btn in self.buttons.items():
|
||||
btn.setChecked(old_value == value)
|
||||
|
||||
|
||||
class IntegerGroup(GroupBase):
|
||||
MIN = None
|
||||
MAX = None
|
||||
"""整数输入分组类,继承自GroupBase,用于整数型配置(如数值范围)"""
|
||||
|
||||
MIN = None # 输入最小值(子类必须重写)
|
||||
MAX = None # 输入最大值(子类必须重写)
|
||||
|
||||
def __init__(self):
|
||||
"""初始化整数输入分组,创建数值输入框并关联配置"""
|
||||
# 调用父类GroupBase的构造函数
|
||||
super().__init__()
|
||||
|
||||
# 创建整数输入框(QSpinBox)
|
||||
self.input = QtWidgets.QSpinBox()
|
||||
# 设置输入框的数值范围(从子类的MIN和MAX获取)
|
||||
self.input.setRange(self.MIN, self.MAX)
|
||||
# 从配置读取默认值,更新输入框显示
|
||||
self.set_value(self.settings.valueOrDefault(self.KEY))
|
||||
# 连接输入框“值变更”信号到处理函数
|
||||
self.input.valueChanged.connect(self.on_value_changed)
|
||||
# 将输入框添加到分组布局
|
||||
self.layout.addWidget(self.input)
|
||||
# 添加弹性拉伸,将输入框顶到布局上方
|
||||
self.layout.addStretch(100)
|
||||
# 初始化“忽略值变更”标记为False(无需初始化规避,因set_value在信号连接前调用)
|
||||
self.ignore_value_changed = False
|
||||
|
||||
def set_value(self, value):
|
||||
"""根据配置值更新整数输入框的显示
|
||||
|
||||
参数:
|
||||
value: 要设置的整数配置值
|
||||
"""
|
||||
# 设置输入框的当前值
|
||||
self.input.setValue(value)
|
||||
|
||||
|
||||
class SingleCheckboxGroup(GroupBase):
|
||||
LABEL = None
|
||||
"""单复选框分组类,继承自GroupBase,用于布尔型配置(是/否选项)"""
|
||||
|
||||
LABEL = None # 复选框显示文本(子类必须重写)
|
||||
|
||||
def __init__(self):
|
||||
"""初始化复选框分组,创建复选框并关联配置"""
|
||||
# 调用父类GroupBase的构造函数
|
||||
super().__init__()
|
||||
|
||||
# 创建复选框,设置显示文本(从子类的LABEL获取)
|
||||
self.input = QtWidgets.QCheckBox(self.LABEL)
|
||||
# 从配置读取默认值,更新复选框选中状态
|
||||
self.set_value(self.settings.valueOrDefault(self.KEY))
|
||||
# 连接复选框“状态变更”信号到处理函数
|
||||
self.input.checkStateChanged.connect(self.on_value_changed)
|
||||
# 将复选框添加到分组布局
|
||||
self.layout.addWidget(self.input)
|
||||
# 添加弹性拉伸,将复选框顶到布局上方
|
||||
self.layout.addStretch(100)
|
||||
# 初始化“忽略值变更”标记为False
|
||||
self.ignore_value_changed = False
|
||||
|
||||
def set_value(self, value):
|
||||
"""根据配置值更新复选框的选中状态
|
||||
|
||||
参数:
|
||||
value: 布尔值,True=选中,False=未选中
|
||||
"""
|
||||
# 设置复选框的选中状态
|
||||
self.input.setChecked(value)
|
||||
|
||||
def convert_value_from_qt(self, value):
|
||||
"""将复选框的Qt状态值转换为布尔值
|
||||
|
||||
参数:
|
||||
value: Qt.CheckState枚举值(Checked/Unchecked/PartiallyChecked)
|
||||
|
||||
返回:
|
||||
布尔值,True=选中(Checked),False=其他状态
|
||||
"""
|
||||
# 仅当状态为“Checked”时返回True,其他状态返回False
|
||||
return value == Qt.CheckState.Checked
|
||||
|
||||
|
||||
class ArrangeDefaultWidget(RadioGroup):
|
||||
TITLE = 'Default Arrange Method:'
|
||||
HELPTEXT = ('How images are arranged when inserted in batch')
|
||||
KEY = 'Items/arrange_default'
|
||||
"""“默认排列方式”配置项,继承自RadioGroup(单选按钮分组)"""
|
||||
TITLE = 'Default Arrange Method:' # 分组标题
|
||||
HELPTEXT = ('How images are arranged when inserted in batch') # 帮助文本
|
||||
KEY = 'Items/arrange_default' # 关联的配置键(对应BeeSettings的FIELDS)
|
||||
# 单选按钮选项:(配置值, 按钮文本, 按钮提示)
|
||||
OPTIONS = (
|
||||
('optimal', 'Optimal', 'Arrange Optimal'),
|
||||
('optimal', 'Optimal', 'Arrange Optimal'), # 优化排列
|
||||
('horizontal', 'Horizontal (by filename)',
|
||||
'Arrange Horizontal (by filename)'),
|
||||
'Arrange Horizontal (by filename)'), # 按文件名水平排列
|
||||
('vertical', 'Vertical (by filename)',
|
||||
'Arrange Vertical (by filename)'),
|
||||
('square', 'Square (by filename)', 'Arrannge Square (by filename)'))
|
||||
'Arrange Vertical (by filename)'), # 按文件名垂直排列
|
||||
('square', 'Square (by filename)', 'Arrannge Square (by filename)')) # 按文件名方形排列
|
||||
|
||||
|
||||
class ImageStorageFormatWidget(RadioGroup):
|
||||
TITLE = 'Image Storage Format:'
|
||||
"""“图像存储格式”配置项,继承自RadioGroup(单选按钮分组)"""
|
||||
TITLE = 'Image Storage Format:' # 分组标题
|
||||
# 帮助文本(说明格式作用及生效时机)
|
||||
HELPTEXT = ('How images are stored inside bee files.'
|
||||
' Changes will only take effect on newly saved images.')
|
||||
KEY = 'Items/image_storage_format'
|
||||
KEY = 'Items/image_storage_format' # 关联的配置键
|
||||
# 单选按钮选项
|
||||
OPTIONS = (
|
||||
('best', 'Best Guess',
|
||||
('Small images and images with alpha channel are stored as png,'
|
||||
' everything else as jpg')),
|
||||
('png', 'Always PNG', 'Lossless, but large bee file'),
|
||||
' everything else as jpg')), # 自动判断:小图/透明图存PNG,其他存JPG
|
||||
('png', 'Always PNG', 'Lossless, but large bee file'), # 始终存PNG(无损,文件大)
|
||||
('jpg', 'Always JPG',
|
||||
'Small bee file, but lossy and no transparency support'))
|
||||
'Small bee file, but lossy and no transparency support')) # 始终存JPG(文件小,有损无透明)
|
||||
|
||||
|
||||
class ArrangeGapWidget(IntegerGroup):
|
||||
TITLE = 'Arrange Gap:'
|
||||
HELPTEXT = ('The gap between images when using arrange actions.')
|
||||
KEY = 'Items/arrange_gap'
|
||||
MIN = 0
|
||||
MAX = 200
|
||||
"""“排列间隙”配置项,继承自IntegerGroup(整数输入分组)"""
|
||||
TITLE = 'Arrange Gap:' # 分组标题
|
||||
HELPTEXT = ('The gap between images when using arrange actions.') # 帮助文本
|
||||
KEY = 'Items/arrange_gap' # 关联的配置键
|
||||
MIN = 0 # 最小间隙(0像素)
|
||||
MAX = 200 # 最大间隙(200像素)
|
||||
|
||||
|
||||
class AllocationLimitWidget(IntegerGroup):
|
||||
TITLE = 'Maximum Image Size:'
|
||||
"""“最大图像尺寸”配置项,继承自IntegerGroup(整数输入分组)"""
|
||||
TITLE = 'Maximum Image Size:' # 分组标题
|
||||
# 帮助文本(说明尺寸单位及无限制设置)
|
||||
HELPTEXT = ('The maximum image size that can be loaded (in megabytes). '
|
||||
'Set to 0 for no limitation.')
|
||||
KEY = 'Items/image_allocation_limit'
|
||||
MIN = 0
|
||||
MAX = 10000
|
||||
KEY = 'Items/image_allocation_limit' # 关联的配置键
|
||||
MIN = 0 # 最小值(0=无限制)
|
||||
MAX = 10000 # 最大值(10000MB)
|
||||
|
||||
|
||||
class ConfirmCloseUnsavedWidget(SingleCheckboxGroup):
|
||||
TITLE = 'Confirm when closing an unsaved file:'
|
||||
"""“关闭未保存文件确认”配置项,继承自SingleCheckboxGroup(复选框分组)"""
|
||||
TITLE = 'Confirm when closing an unsaved file:' # 分组标题
|
||||
# 帮助文本(说明确认功能的作用)
|
||||
HELPTEXT = (
|
||||
'When about to close an unsaved file, should BeeRef ask for '
|
||||
'confirmation?')
|
||||
LABEL = 'Confirm when closing'
|
||||
KEY = 'Save/confirm_close_unsaved'
|
||||
LABEL = 'Confirm when closing' # 复选框显示文本
|
||||
KEY = 'Save/confirm_close_unsaved' # 关联的配置键
|
||||
|
||||
|
||||
class SettingsDialog(QtWidgets.QDialog):
|
||||
"""设置对话框类,继承自QDialog(模态对话框),整合所有配置项"""
|
||||
|
||||
def __init__(self, parent):
|
||||
"""初始化设置对话框,创建标签页和按钮
|
||||
|
||||
参数:
|
||||
parent: 父窗口对象(确保对话框模态关联父窗口)
|
||||
"""
|
||||
# 调用父类QDialog的构造函数,指定父窗口
|
||||
super().__init__(parent)
|
||||
# 设置对话框标题(含应用名称)
|
||||
self.setWindowTitle(f'{constants.APPNAME} Settings')
|
||||
# 创建标签页组件(用于分类显示配置项)
|
||||
tabs = QtWidgets.QTabWidget()
|
||||
|
||||
# Miscellaneous
|
||||
# ---------------------- 第一个标签页:Miscellaneous(杂项) ----------------------
|
||||
# 创建杂项标签页的容器 widget
|
||||
misc = QtWidgets.QWidget()
|
||||
# 创建网格布局(用于排列杂项配置项)
|
||||
misc_layout = QtWidgets.QGridLayout()
|
||||
# 将布局应用到杂项容器
|
||||
misc.setLayout(misc_layout)
|
||||
# 添加“关闭未保存文件确认”配置项到网格布局(第0行第0列)
|
||||
misc_layout.addWidget(ConfirmCloseUnsavedWidget(), 0, 0)
|
||||
# 将杂项标签页添加到标签页组件,设置标签文本(&用于快捷键,Alt+M)
|
||||
tabs.addTab(misc, '&Miscellaneous')
|
||||
|
||||
# Images & Items
|
||||
# ---------------------- 第二个标签页:Images & Items(图像与项目) ----------------------
|
||||
# 创建图像与项目标签页的容器 widget
|
||||
items = QtWidgets.QWidget()
|
||||
# 创建网格布局(用于排列图像相关配置项)
|
||||
items_layout = QtWidgets.QGridLayout()
|
||||
# 将布局应用到图像与项目容器
|
||||
items.setLayout(items_layout)
|
||||
items_layout.addWidget(ImageStorageFormatWidget(), 0, 0)
|
||||
items_layout.addWidget(AllocationLimitWidget(), 0, 1)
|
||||
items_layout.addWidget(ArrangeGapWidget(), 1, 0)
|
||||
items_layout.addWidget(ArrangeDefaultWidget(), 1, 1)
|
||||
# 添加配置项到网格布局(按行/列排列)
|
||||
items_layout.addWidget(ImageStorageFormatWidget(), 0, 0) # 图像存储格式(第0行第0列)
|
||||
items_layout.addWidget(AllocationLimitWidget(), 0, 1) # 最大图像尺寸(第0行第1列)
|
||||
items_layout.addWidget(ArrangeGapWidget(), 1, 0) # 排列间隙(第1行第0列)
|
||||
items_layout.addWidget(ArrangeDefaultWidget(), 1, 1) # 默认排列方式(第1行第1列)
|
||||
# 将图像与项目标签页添加到标签页组件,设置标签文本(&用于快捷键,Alt+I)
|
||||
tabs.addTab(items, '&Images && Items')
|
||||
|
||||
# ---------------------- 对话框主布局 ----------------------
|
||||
# 创建垂直布局(用于排列标签页和底部按钮)
|
||||
layout = QtWidgets.QVBoxLayout()
|
||||
# 将布局应用到对话框
|
||||
self.setLayout(layout)
|
||||
# 将标签页组件添加到主布局
|
||||
layout.addWidget(tabs)
|
||||
|
||||
# Bottom row of buttons
|
||||
# ---------------------- 底部按钮区 ----------------------
|
||||
# 创建对话框按钮盒,包含“关闭”按钮(标准按钮)
|
||||
buttons = QtWidgets.QDialogButtonBox(
|
||||
QtWidgets.QDialogButtonBox.StandardButton.Close)
|
||||
# 连接“关闭”按钮的“拒绝”信号到对话框的关闭函数(reject()会关闭对话框)
|
||||
buttons.rejected.connect(self.reject)
|
||||
|
||||
# 创建“恢复默认值”按钮(自定义按钮)
|
||||
reset_btn = QtWidgets.QPushButton('&Restore Defaults')
|
||||
# 取消按钮的“自动默认”属性(避免按Enter键触发)
|
||||
reset_btn.setAutoDefault(False)
|
||||
# 连接“恢复默认值”按钮的点击信号到处理函数
|
||||
reset_btn.clicked.connect(self.on_restore_defaults)
|
||||
# 将“恢复默认值”按钮添加到按钮盒,指定按钮角色为“动作角色”
|
||||
buttons.addButton(reset_btn,
|
||||
QtWidgets.QDialogButtonBox.ButtonRole.ActionRole)
|
||||
|
||||
# 将按钮盒添加到主布局(位于标签页下方)
|
||||
layout.addWidget(buttons)
|
||||
# 显示对话框(模态显示,阻塞父窗口操作)
|
||||
self.show()
|
||||
|
||||
def on_restore_defaults(self, *args, **kwargs):
|
||||
"""“恢复默认值”按钮的点击处理函数,弹出确认对话框并执行恢复"""
|
||||
# 弹出确认对话框,询问用户是否恢复所有默认设置
|
||||
reply = QtWidgets.QMessageBox.question(
|
||||
self,
|
||||
'Restore defaults?',
|
||||
'Do you want to restore all settings to their default values?')
|
||||
self, # 父窗口(当前设置对话框)
|
||||
'Restore defaults?', # 对话框标题
|
||||
'Do you want to restore all settings to their default values?') # 对话框内容
|
||||
|
||||
# 若用户点击“是”(Yes),执行恢复默认设置
|
||||
if reply == QtWidgets.QMessageBox.StandardButton.Yes:
|
||||
BeeSettings().restore_defaults()
|
||||
# 实例化配置对象并调用恢复默认方法
|
||||
BeeSettings().restore_defaults()
|
||||
Loading…
Reference in a new issue