diff --git a/.gitignore b/.gitignore index 0c545709..cb43d78d 100644 --- a/.gitignore +++ b/.gitignore @@ -410,12 +410,8 @@ updater.old/ updaterback/ data/ key.json -gui/dev_list/* -!gui/dev_list/in_dev res/packages/ Gui/packages.json - -dev.dat *.zip *.7z source/ @@ -743,4 +739,6 @@ vite.config.js.timestamp-* vite.config.ts.timestamp-* cache/ -source/ \ No newline at end of file +source/ + +demo/ \ No newline at end of file diff --git a/Gui/install_pack.py b/Gui/install_pack.py index 18e25075..10d75b8e 100644 --- a/Gui/install_pack.py +++ b/Gui/install_pack.py @@ -4,7 +4,7 @@ app = QApplication(sys.argv) from uiStyles.QUI import * -from uiStyles import PagesUI, UMessageBox, MessageButtonTemplate, styles, maps, UCheckBox +from uiStyles import PagesUI, UMessageBox, MessageButton, styles, maps, UCheckBox import pyperclip from sharelibs import (get_lang, settings, get_inst_lang, get_icon, system_lang, parse_system_language_to_lang_id, run_software, get_resource_path, is_admin, get_init_lang, QtThread, mem_id) import win32com.client @@ -143,8 +143,8 @@ def new_msg(parent, title: str, text: str, icon: QMessageBox.Icon, - buttons: MessageButtonTemplate = MessageButtonTemplate.OK, - defaultButton: MessageButtonTemplate = MessageButtonTemplate.OK): + buttons: MessageButton = MessageButton.OK, + defaultButton: MessageButton = MessageButton.OK): msg_box = UMessageBox.new_msg(parent, title, text, icon, buttons, defaultButton) new_color_bar(msg_box) @@ -216,7 +216,7 @@ def apply_titleBar(self, window: QMainWindow | QDialog): '''应用标题栏样式''' hwnd = window.winId().__int__() - if select_styles.css_data['.meta']['mode'] == 'dark': + if select_styles.css_data['.meta']['--mode'] == 'dark': is_dark_mode = 1 else: is_dark_mode = 0 @@ -781,7 +781,7 @@ def on_next(self): self, get_ipk_lang('1a'), get_ipk_lang('24').format('\n'.join(self.changes if self.changes else [get_ipk_lang('2f')])), - MessageButtonTemplate.YESNO, + MessageButton.YESNO, ) for i in packages_info: @@ -793,7 +793,7 @@ def on_next(self): self.changes = get_list_diff(select_package_id, package_id_list) - if message == 3: + if message == MessageButton.NO: return else: message =QMessageBox.question( diff --git a/Gui/logger.py b/Gui/logger.py index e8ec116d..956e6e81 100644 --- a/Gui/logger.py +++ b/Gui/logger.py @@ -49,6 +49,12 @@ def remove_old_log(directory_path): os.remove(file_path) except Exception as e: pass + +class ExceptionVal: + raise_trace = 0b1 + output_msg = 0b10 + exit = 0b100 + all = raise_trace | output_msg | exit # 定义日志路径 folder_path = Path("cache/logs") @@ -120,7 +126,7 @@ def __init__(self, default_service_id): self.info(f'logger Started') remove_old_log(folder_path) - def auto_logger(self, service_id=None, start_extra_text='', end_extra_text='', start_extra=None, end_extra=None, level=logging.INFO): + def auto_logger(self, service_id: str | None=None, parent:list | None=None , start_extra_text: str='', end_extra_text:str='', start_extra:str | None=None, end_extra: str | None=None, level=logging.INFO): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): @@ -140,19 +146,21 @@ def wrapper(*args, **kwargs): out_service_id = self.default_service_id if service_id is None else service_id start_extra_out = {} if start_extra is None else start_extra end_extra_out = {} if end_extra is None else end_extra + parent_out = [] if parent is None else parent + + function_name = '.'.join(parent_out) + ('.' if parent else '') + func.__name__ - log_func(f'Running function: {func.__name__} args={args} kwargs={kwargs}', extra={'service_id': out_service_id, **start_extra_out}) + log_func(f'Running function: {function_name} args={args} kwargs={kwargs}', extra={'service_id': out_service_id, **start_extra_out}) if start_extra_text: log_func(start_extra_text, extra={'service_id': out_service_id, **start_extra_out}) try: result = func(*args, **kwargs) except Exception as e: trace = traceback.format_exc() - self.exception(out_service_id, trace, f'Function {func.__name__} raised an unexpected exception', extra={'service_id': out_service_id, **end_extra_out}) - QMessageBox.critical(None, 'An unexpected error occurred', f'An unexpected error occurred in {func.__name__}: \n{trace}\nPlease look the log path: {(folder_path / f"{log_id}.log").resolve()} and send me the log file on issue: https://github.com/xystudiocode/pyClickMouse/issues/new/choose') + self.exception(out_service_id, trace, f'Function {function_name} raised an unexpected exception', extra={'service_id': out_service_id, **end_extra_out}) + QMessageBox.critical(None, 'An unexpected error occurred', f'An unexpected error occurred in {function_name}: \n{trace}\nPlease look the log path: {(folder_path / f"{log_id}.log").resolve()} and send me the log file on issue: https://github.com/xystudiocode/pyClickMouse/issues/new/choose') sys.exit(1) # 退出程序 - raise e - log_func(f'Function {func.__name__} running successfull, returned: {result}', extra={'service_id': out_service_id, **end_extra_out}) + log_func(f'Function {function_name} running successfull, returned: {result}', extra={'service_id': out_service_id, **end_extra_out}) if end_extra_text: log_func(end_extra_text, extra={'service_id': out_service_id, **end_extra_out}) return result @@ -174,5 +182,13 @@ def error(self, msg, extra=None): def critical(self, msg, extra=None): self.logger.critical(msg, extra=extra) - def exception(self, service, trace, msg='', extra=None): - self.critical(f'{msg}: An error occurred in {service}\n{trace}', extra=extra) \ No newline at end of file + def exception(self, service, msg='', extra=None, mode: ExceptionVal = ExceptionVal.raise_trace): + if mode & ExceptionVal.raise_trace: # 获取堆栈 + trace = '\n' + traceback.format_exc() + else: + trace = '' + self.critical(f'{msg}: An error occurred in {service}:{trace}', extra=extra) + if mode & ExceptionVal.output_msg: # 输出消息 + QMessageBox.critical(None, 'An unexpected error occurred', f'An unexpected error occurred: {trace}\nPlease look the log path: {(folder_path / f"{log_id}.log").resolve()} and send me the log file on issue: https://github.com/xystudiocode/pyClickMouse/issues/new/choose') + if mode & ExceptionVal.exit: # 退出程序 + sys.exit(1) # 退出程序 diff --git a/Gui/main.py b/Gui/main.py index 3b50fc2f..8daf7da5 100644 --- a/Gui/main.py +++ b/Gui/main.py @@ -1,23 +1,25 @@ # 加载ui框架 from PySide6.QtWidgets import QApplication import sys -from logger import Logger, logging +from logger import Logger, logging, ExceptionVal app = QApplication(sys.argv) logger = Logger('clickmouse.main') -from uiStyles.QUI import * # 加载框架 +try: + from sharelibs import * # 共享库 +except: + logger.exception('clickmouse.main', mode=ExceptionVal.all) +from uiStyles.QUI import * +from uiStyles import * from datetime import datetime # 检查时间 from pynput import keyboard # 热键功能库 import pyautogui # 鼠标操作库 from time import sleep, time # 延迟 from webbrowser import open as open_url # 关于作者 from check_update import check_update, web_data, download_file # 更新检查 -from uiStyles import (UnitInputLayout, styles, maps, StyleReplaceMode, ULabel, CustonMessageButton, SelectUI, UCheckBox, UMessageBox, MessageButtonTemplate) # 软件界面样式 -from uiStyles import indexes as style_indexes # 界面组件样式索引 -from sharelibs import * # 共享库 +from uiStyles import indexes as style_indexes from sharelibs import __version__ # 版本号 -import parse_dev # 解析开发固件配置 import winreg # 注册表库 import math # 数学库 import colorsys # 颜色库 @@ -179,14 +181,6 @@ def get_windows_accent_color(): # 通常我们使用RGB格式,忽略Alpha通道 return f'#{r_str}{g_str}{b_str}' -@logger.auto_logger('clickmouse.colorGetter') -def new_color_bar(obj): - ''' - 给创建添加样式标题栏 - ''' - color_getter.style_changed.connect(lambda: color_getter.apply_titleBar(obj)) - color_getter.style_changed.emit() - @logger.auto_logger('clickmouse.colorGetter') def lighten_color_hex(hex_color, factor): ''' @@ -374,7 +368,7 @@ def on_input_change(*, type:str ): delay_num = setting_value.click_delay time_num = setting_value.click_times is_error = False - elif type ==InputChange.setting_window: + elif type == InputChange.setting_window: delay_text = setting_window.default_delay delay_times = setting_window.default_time total = setting_window.total_time_label @@ -387,14 +381,14 @@ def on_input_change(*, type:str ): delay_times.setEnabled(not(times_combo.currentIndex() == latest_index or (setting_value.times_unit == latest_index) and type == InputChange.main_window)) - if times_combo.currentIndex() == latest_index or input_times == '0': + if times_combo.currentIndex() == latest_index: is_inf = True if setting_value.times_unit == latest_index and type == InputChange.main_window: is_inf = True def on_delay_error(error_text=get_lang('14')): '''输入延迟错误''' - logger.warning(f'Input delay error: {error_text}', extra={'service_id': 'clickmouse.main.ui'}) + logger.debug(f'Input delay error: {error_text}', extra={'service_id': 'clickmouse.main.ui'}) total.setText(f'{get_lang('2c')}: {error_text}') if type == InputChange.main_window: global is_error @@ -402,10 +396,16 @@ def on_delay_error(error_text=get_lang('14')): main_window.right_click_button.setEnabled(False) main_window.left_click_button.setEnabled(False) is_error = True + elif type == InputChange.setting_window: + if error_text == get_lang('14'): + text = get_lang('60') + else: + text = error_text + main_window.total_time_label.setText(f'{get_lang('2c')}: {text}') + @logger.auto_logger('clickmouse.main.ui', level=logging.DEBUG) def check_default_var(value): '''检查默认延迟是否有效''' - logger.debug('Checking default var.', extra={'service_id': 'clickmouse.main.ui'}) try: var = int(settings.get(f'click_{value}', '')) if not var: @@ -420,147 +420,170 @@ def check_default_var(value): on_delay_error() return False - logger.debug('Checking input delay.', extra={'service_id': 'clickmouse.main.ui'}) - try: - delay = math.ceil(float(input_delay)) - if delay < 1: - raise ValueError - except ValueError: - if not setting_value.click_delay == '': - if input_delay == '': - if check_default_var('delay'): - delay = int(setting_value.click_delay) - else: - return - elif setting_value.delay_error_use_default: - if check_default_var('delay'): - delay = int(setting_value.click_delay) - else: - return - else: - on_delay_error() - return - except Exception: - on_delay_error() - return - - logger.debug('Checking input times.', extra={'service_id': 'clickmouse.main.ui'}) - if not is_inf: + @logger.auto_logger('clickmouse.main.ui', level=logging.DEBUG) + def get_num(input_value, value_default, err_use_default, default_var): + value = None try: - times = math.ceil(float(input_times)) - if times < 1: + value = math.ceil(float(input_value)) + if value < 1: raise ValueError except ValueError: - if setting_value.click_times == '' and setting_value.click_delay == '': - on_delay_error(get_lang('61')) - return - else: - if input_times == '': - if check_default_var('times'): - times = int(setting_value.click_times) - else: - return - elif setting_value.times_error_use_default: - if check_default_var('times'): - times = int(setting_value.click_times) + if value_default: + if input_delay == '' or err_use_default: + if check_default_var(default_var): + value = int(value_default) else: return else: - on_delay_error() return except Exception: - on_delay_error() return + return value + + delay = get_num(input_delay, setting_value.click_delay, setting_value.delay_error_use_default, 'delay') + + if not is_inf: + if not(setting_value.click_times) and not(setting_value.click_delay): + on_delay_error(get_lang('61')) + return 1 + times = get_num(input_times, setting_value.click_times, setting_value.times_error_use_default, 'times') + if times is None: + on_delay_error() + return -1 + if delay is None: + on_delay_error() + return -1 if type == InputChange.main_window: + # 先过滤前面的报错 main_window.right_click_button.setEnabled(True) main_window.left_click_button.setEnabled(True) is_error = False + if bool(delay_text.text()) ^ bool(delay_times.text()): # 状态不同 + if not((setting_value.modify_using_default_input or is_inf) and dev_flags.get('new_settings')): + on_delay_error() + return -2 + if ((delay_combo.currentIndex() != setting_value.delay_unit) and not(delay_text.text())): + if not(setting_value.modify_using_default_combo and dev_flags.get('new_settings')): + on_delay_error() + return -2 + if ((times_combo.currentIndex() != setting_value.times_unit or is_inf) and not(delay_text.text())): + if not(setting_value.modify_using_default_combo and dev_flags.get('new_settings')): + on_delay_error() + return -2 + + match delay_combo.currentIndex(): + case 0: + delay_num = delay + case 1: + delay_num = delay * 1000 + case 2: + delay_num = delay * 60 * 1000 + case _: + delay_num = delay - if setting_value.click_delay != '' and input_delay == '': - match setting_value.delay_unit: - case 0: - delay_num = delay - case 1: - delay_num = delay * 1000 + if is_inf: + total.setText(f'{get_lang('2c')}: {get_lang('2b')}') else: - match delay_combo.currentIndex(): + match times_combo.currentIndex(): case 0: - delay_num = delay + time_num = times case 1: - delay_num = delay * 1000 + time_num = times * 10000 case 2: - delay_num = delay * 60 * 1000 + time_num = times * 100_0000 case _: - delay_num = delay - - if is_inf: - total.setText(f'{get_lang('2c')}: {get_lang('2b')}') - if type == InputChange.main_window: - if delay_num == 0: - on_delay_error() - else: - if setting_value.click_times != '' and input_times == '': - match setting_value.times_unit: - case 0: - time_num = times - case 1: - time_num = times * 10000 - else: - match times_combo.currentIndex(): - case 0: - time_num = times - case 1: - time_num = times * 10000 - case 2: - time_num = times * 100_0000 - case _: - time_num = times - - if (delay_num == 0 and time_num != 0) or (delay_num != 0 and time_num == 0): - on_delay_error() - return + time_num = times try: total_run_time = get_unit_value(delay_num * time_num) + if type == InputChange.setting_window: + main_window.delay_combo.setCurrentIndex(delay_combo.currentIndex()) + main_window.times_combo.setCurrentIndex(times_combo.currentIndex()) except OverflowError: on_delay_error(get_lang('67')) - return + return -3 total.setText(f'{get_lang('2c')}: {total_run_time[0]}{total_run_time[1]}') + if type == InputChange.setting_window: + on_input_change(type=InputChange.main_window) # 刷新主窗口 + return 0 class UMainWindow(QMainWindow): '''自定义窗口基类''' def __init__(self): - logger.debug('Initializing window.') + logger.info('Initializing window.', extra={'service_id': 'clickmouse.ui'}) super().__init__() self.setWindowIcon(icon) - new_color_bar(self) - + self.func = lambda: color_getter.apply_titleBar(self) + + self.addtional_local_value = {'self': self} + logger.info('Window initialized.', extra={'service_id': 'clickmouse.ui'}) + + @logger.auto_logger('clickmouse.ui', ['UMainWindow']) def showEvent(self, event): '''窗口显示事件''' - QTimer.singleShot(setting_value.soft_delay, lambda: new_color_bar(self)) + color_getter.style_changed.connect(self.func) + QTimer.singleShot(setting_value.soft_delay, self.func) return super().showEvent(event) + @logger.auto_logger('clickmouse.ui', ['UMainWindow']) + def closeEvent(self, event): + '''窗口关闭事件''' + color_getter.style_changed.disconnect(self.func) + return super().closeEvent(event) + class UDialog(QDialog): '''自定义对话框基类''' - def __init__(self, parent=None): - logger.debug('Initializing window.') + def __init__(self): + logger.debug('Initializing window.', extra={'service_id': 'clickmouse.ui'}) - super().__init__(parent=parent) + super().__init__() self.setWindowIcon(icon) - new_color_bar(self) + self.func = lambda: color_getter.apply_titleBar(self) + + self.addtional_local_value = {'self': self} + + logger.debug('Window initialized.', extra={'service_id': 'clickmouse.ui'}) + @logger.auto_logger('clickmouse.ui', ['UDialog']) def showEvent(self, event): '''窗口显示事件''' - QTimer.singleShot(setting_value.soft_delay, lambda:new_color_bar(self)) + color_getter.style_changed.connect(self.func) + QTimer.singleShot(setting_value.soft_delay, self.func) return super().showEvent(event) + + @logger.auto_logger('clickmouse.ui', ['UDialog']) + def closeEvent(self, event): + '''窗口关闭事件''' + color_getter.style_changed.disconnect(self.func) + return super().closeEvent(event) + +class MessageBox(UMessageBox): + def __init__(self, parent: QWidget | None, title: str, text: str, icon: MessageIcon, buttons: MessageButton, defaultButton: MessageButton): + super().__init__(parent, title, text, icon, buttons, defaultButton) + self.func = lambda: color_getter.apply_titleBar(self) + + @logger.auto_logger('clickmouse.ui', ['MessageBox']) + def showEvent(self, event): + color_getter.style_changed.connect(self.func) + evt = super().showEvent(event) + QTimer.singleShot(setting_value.soft_delay, self.func) + return evt + + @logger.auto_logger('clickmouse.ui', ['MessageBox']) + def done(self, result): + '''关闭对话框时执行''' + color_getter.style_changed.disconnect(self.func) + return super().done(result) class StartManager(QObject): '''开机自启动管理器''' updated = Signal(bool) + def __init__(self): super().__init__() + logger.info('Starting startup manager.', extra={'service_id': 'clickmouse.setting.startupManager'}) self.app_name = 'clickmouse.lnk' self.status_path = r'Software\Microsoft\Windows\CurrentVersion\Explorer\StartupApproved\StartupFolder' self.create_reg() @@ -569,55 +592,53 @@ def __init__(self): self.timer = QTimer() self.timer.timeout.connect(self.check_value) self.timer.start(setting_value.soft_delay) + logger.info('Startup manager initialized.', extra={'service_id': 'clickmouse.setting.startupManager'}) + @logger.auto_logger('clickmouse.setting.startupManager', ['StartManager'], start_extra_text='Startup folder`s shortcut is not found') def create_reg(self): '''检查是否已启用开机自启动''' start_path = Path(os.environ['APPDATA'], 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup', self.app_name) if not(start_path.exists()): + logger.warning('Startup folder`s shortcut is not found,creating shortcut.', extra={'service_id': 'clickmouse.setting.startupManager'}) create_shortcut(str(start_path), str(Path.cwd() / 'main.exe') + ' --quiet', 'ClickMouse', work_dir=str(Path.cwd())) self.disable() - + else: + logger.info('Startup folder`s shortcut is found.', extra={'service_id': 'clickmouse.setting.startupManager'}) + + @logger.auto_logger('clickmouse.setting.startupManager', ['StartManager'], level=logging.DEBUG) def is_enabled(self): try: with winreg.OpenKey(winreg.HKEY_CURRENT_USER, self.status_path, 0, winreg.KEY_READ) as key: + logger.debug('Try to read value from registry..', extra={'service_id': 'clickmouse.setting.startupManager'}) value, _ = winreg.QueryValueEx(key, self.app_name) return value[0] == 2 except FileNotFoundError: + logger.warning('Registry path not found.', extra={'service_id': 'clickmouse.setting.startupManager'}) return False + @logger.auto_logger('clickmouse.setting.startupManager', ['StartManager'], level=logging.DEBUG) def check_value(self): - '''检查注册表值是否最新''' + '''检查注册表值是否更新''' new_value = self.is_enabled() if new_value != self.auto_start: self.auto_start = new_value self.updated.emit(self.auto_start) + logger.info('Startup value updated.', extra={'service_id': 'clickmouse.setting.startupManager'}) + @logger.auto_logger('clickmouse.setting.startupManager', ['StartManager'], start_extra_text='Enable startup.') def enable(self): '''启用开机自启动''' with winreg.OpenKey(winreg.HKEY_CURRENT_USER, self.status_path, 0, winreg.KEY_WRITE) as key: winreg.SetValueEx(key, self.app_name, 0, winreg.REG_BINARY, bytes([0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])) + @logger.auto_logger('clickmouse.setting.startupManager', ['StartManager'], start_extra_text='Disable startup.') def disable(self): '''禁用开机自启动''' with winreg.OpenKey(winreg.HKEY_CURRENT_USER, self.status_path, 0, winreg.KEY_WRITE) as key: winreg.SetValueEx(key, self.app_name, 0, winreg.REG_BINARY, bytes([0x03, 0x00, 0x00, 0x00]) + get_now_filetime()) - -class MessageBox(UMessageBox): - @staticmethod - def new_msg(parent, - title: str, - text: str, - icon: QMessageBox.Icon, - buttons: MessageButtonTemplate = MessageButtonTemplate.OK, - defaultButton: MessageButtonTemplate = MessageButtonTemplate.OK): - - msg_box = UMessageBox.new_msg(parent, title, text, icon, buttons, defaultButton) - new_color_bar(msg_box) - - return msg_box class UHotkeyLineEdit(QLineEdit): '''能够捕获热键组合的输入框,只有获得焦点时才更新''' @@ -626,7 +647,8 @@ def __init__(self, parent=None): self._connection = None # 保存信号连接对象 self.key_list = [] # 保存按下的热键 self.setReadOnly(True) - self.listener = get_hotkey_listener_instance() + #self.listener = get_hotkey_listener_instance() + self.listener = hotkey_listener def focusInEvent(self, event): '''获得焦点时连接信号''' @@ -660,10 +682,12 @@ class HotkeyListener(QObject): def __init__(self): super().__init__() + logger.info('Initializing hotkey listener.', extra={'service_id': 'clickmouse.setting.hotkeyManager'}) self.listener = None self.is_listening = False self.clicked_keys = set() # 用于跟踪当前按下的键 + @logger.auto_logger('clickmouse.setting.hotkeyManager', ['HotkeyListener']) def start_listening(self): '''开始监听热键''' if self.is_listening: @@ -678,6 +702,7 @@ def start_listening(self): self.listener.daemon = True # 设置为守护线程 self.listener.start() + @logger.auto_logger('clickmouse.setting.hotkeyManager', ['HotkeyListener']) def stop_listening(self): '''停止监听热键''' if self.listener and self.is_listening: @@ -717,33 +742,30 @@ class Click(QObject): def __init__(self): super().__init__() + logger.info('Initializing clicker.', extra={'service_id': 'clickmouse.clicker'}) self.running = False self.paused = False self.click_thread = None self.right_clicked = False self.left_clicked = False + @logger.auto_logger('clickmouse.clicker', ['Click']) def mouse_left(self, delay, times): - logger.info('Left click') if not self.running: self.mouse_click(button='left', input_delay=delay, times=times) + @logger.auto_logger('clickmouse.clicker', ['Click']) def mouse_right(self, delay, times): # 停止当前运行的点击线程 - logger.info('Right click') if not self.running: self.mouse_click(button='right', input_delay=delay, times=times) - def set_default_clicked(self): - self.left_clicked = False - self.right_clicked = False - self.click_changed.emit(self.left_clicked, self.right_clicked) - + @logger.auto_logger('clickmouse.clicker', ['Click'], start_extra_text='Start clicker.') def mouse_click(self, button: str, input_delay, times): '''鼠标连点''' - logger.info('Start click') # 重置状态 if self.click_thread and self.click_thread.isRunning(): + logger.info('Stop clicker') self.running = False self.paused = False self.pause.emit(False) @@ -767,14 +789,16 @@ def mouse_click(self, button: str, input_delay, times): # 判断参数有效性 try: + logger.debug(f'Clicking {button} with delay {input_delay} and times {times}') delay = math.ceil(float(input_delay)) except Exception: - trace = format_exc() + trace = format_exc() # 获取异常堆栈跟踪信息 MessageBox.critical(None, get_lang('14'), f'{get_lang('1b')}\n{trace}') - logger.exception('Clicker', trace) + logger.exception('clickmouse.clicker') return # 创建独立线程避免阻塞GUI + @logger.auto_logger('clickmouse.clicker', ['Click']) def click_loop(): self.pause.emit(False) i = 0 @@ -785,7 +809,8 @@ def click_loop(): break if not self.paused: try: - if times == float('inf'): + logger.debug('Clicking.') + if times == float('inf'): self.click_conuter.emit('inf', str(i), str(delay)) else: self.click_conuter.emit(str(times), str(i), str(delay)) @@ -795,7 +820,7 @@ def click_loop(): except Exception: trace = format_exc() MessageBox.critical(None, get_lang('14'), f'{get_lang('1b')}\n{trace}') - logger.exception('Clicker', trace) + logger.exception('clickmouse.clicker', trace) self.stopped.emit() break @@ -810,6 +835,7 @@ def click_loop(): self.click_thread = QtThread(click_loop) self.click_thread.start() + @logger.auto_logger('clickmouse.clicker', ['Click']) def pause_click(self): if self.paused: logger.info('Clicker resumed') @@ -820,23 +846,25 @@ def pause_click(self): class Refresh: def __init__(self): + logger.info('Initializing refresh service', extra={'service_id': 'clickmouse.refresh'}) self.steps = [ self.refresh_title, self.left_check, self.right_check, ] + @logger.auto_logger('clickmouse.refresh', ['Refresh']) def run(self): - logger.info('Running refresh service') self.do_step(self.steps) - + + @logger.auto_logger('clickmouse.refresh', ['Refresh']) def do_step(self, codes): # 尝试执行代码 for code in codes: - logger.debug(f'Running step {code.__name__}') try: + logger.info(f'Running function: Refresh.{code.__name__}', extra={'service_id': 'clickmouse.refresh'}) code() - logger.debug(f'Step {code.__name__} running successfully.') + logger.info(f'Function {code.__name__} running successfull.') except NameError as e: logger.warning(f'Step {code.__name__} not defined: {e}') except Exception as e: @@ -847,34 +875,38 @@ def refresh_title(self): def left_check(self): if clicker.left_clicked: + logger.info('Left click is started.') set_style(main_window.left_click_button, StyleClass.selected) else: - logger.warning('Left click is not enabled.') + logger.info('Left click is not started.') set_style(main_window.left_click_button, StyleClass.none) def right_check(self): if clicker.right_clicked: + logger.info('Right click is started.') set_style(main_window.right_click_button,StyleClass.selected) else: - logger.warning('Right click is not enabled.') + logger.info('Right click is not started.') set_style(main_window.right_click_button, StyleClass.none) class RunAfter: def __init__(self): + logger.info('Initializing run-after service', extra={'service_id': 'clickmouse.runafter'}) self.program_list = {} + @logger.auto_logger('clickmouse.runafter', ['RunAfter']) def add(self, name, python_path, exe_path, run_as_admin=False): - logger.info('Add run-after plan') self.program_list[name] = (python_path, exe_path, run_as_admin) MessageBox.information(main_window, get_lang('59'), get_lang('5a')) + @logger.auto_logger('clickmouse.runafter', ['RunAfter']) def remove(self, name): logger.info('Remove run-after plan') del self.program_list[name] MessageBox.information(main_window, get_lang('59'), get_lang('88')) + @logger.auto_logger('clickmouse.runafter', ['RunAfter']) def run(self): - logger.info('Running run-after plan') for python_path, exe_path, use_admin in self.program_list.values(): if use_admin: run_as_admin(python_path, exe_path) @@ -885,10 +917,10 @@ class ColorGetter(QObject): style_changed = Signal() def __init__(self): - global refresh - super().__init__() + logger.info('Initializing color getter.', extra={'service_id': 'clickmouse.colorgetter'}) + # 记录当前主题 self.style = setting_value.select_style @@ -903,9 +935,6 @@ def __init__(self): run_software('main.py', 'main.exe') sys.exit(0) - # 加载刷新服务 - refresh = Refresh() - # 初始化时应用一次主题 self.apply_global_theme() @@ -914,9 +943,10 @@ def __init__(self): self.timer.timeout.connect(self.check_and_apply_theme) self.timer.start(setting_value.soft_delay) - def load_theme(self): - logger.debug('Get latest theme') + self.need_refresh = False + @logger.auto_logger('clickmouse.colorgetter', ['ColorGetter'], level=logging.DEBUG) + def load_theme(self): theme = None windows_theme = None windows_color = None @@ -925,14 +955,18 @@ def load_theme(self): if self.style == 0: theme = QApplication.styleHints().colorScheme() if theme == Qt.ColorScheme.Dark: + logger.debug('Dark theme') theme = 'auto-dark' elif theme == Qt.ColorScheme.Light: + logger.debug('Light theme') theme = 'auto-light' windows_theme = QApplication.styleHints().colorScheme() if theme == Qt.ColorScheme.Dark: + logger.debug('Dark theme') windows_theme = 'dark' elif theme == Qt.ColorScheme.Light: + logger.debug('Light theme') windows_theme = 'light' windows_color = get_windows_accent_color() @@ -940,10 +974,12 @@ def load_theme(self): for k, v in maps.items(): if v == setting_value.select_style: + logger.debug(f'Found theme: {k}') theme = k return theme, windows_theme, windows_color, use_windows_color + @logger.auto_logger('clickmouse.colorgetter', ['ColorGetter'], level=logging.DEBUG) def check_and_apply_theme(self): '''检查主题是否变化,变化则重新应用''' logger.debug('Check theme') @@ -953,31 +989,37 @@ def check_and_apply_theme(self): new_theme, new_windows_theme, new_windows_color, new_use_windows_color = self.load_theme() if new_theme != self.current_theme: + logger.info('Theme changed') self.current_theme = new_theme self.apply_global_theme() if new_windows_color != self.windows_color: + logger.info('Windows color changed') self.windows_color = new_windows_color self.apply_global_theme() if new_windows_theme != self.windows_theme: + logger.info('Windows theme changed') self.windows_theme = new_windows_theme - self.refresh() + self.need_refresh = True if new_use_windows_color != self.use_windows_color: + logger.info('Windows color changed') self.use_windows_color = new_use_windows_color self.apply_global_theme() - def refresh(self): - refresh.run() - + if self.need_refresh and init_success: + refresh.run() # 刷新 + self.need_refresh = False + + @logger.auto_logger('clickmouse.colorgetter', ['ColorGetter']) def apply_titleBar(self, window: QMainWindow | QDialog): '''应用标题栏样式''' - logger.debug('Apply titleBar style') - + if not init_success: # 等待初始化完成加载 + return -1 hwnd = window.winId().__int__() - if select_styles.css_data['.meta']['--mode'] == 'dark': + if select_styles.css_data['.meta']['mode'] == 'dark': is_dark_mode = 1 else: is_dark_mode = 0 @@ -990,14 +1032,14 @@ def apply_titleBar(self, window: QMainWindow | QDialog): ctypes.sizeof(wintypes.INT) ) + return 0 + + @logger.auto_logger('clickmouse.colorgetter', ['ColorGetter']) def apply_global_theme(self): '''根据当前主题,为整个应用设置全局样式表''' global select_styles - logger.info('Use style') - app = get_application_instance() - self.style_changed.emit() current_theme = self.current_theme.replace('auto-', '') @@ -1007,7 +1049,7 @@ def apply_global_theme(self): steps = [ [['.selected:pressed', 'background-color'], lighten_color_hex(self.windows_color, -0.165)] ] - if select_styles.css_data['.meta']['--mode'] == 'dark': + if select_styles.css_data['.meta']['mode'] == 'dark': steps.extend([ [['.selected', 'background-color'], lighten_color_hex(self.windows_color, 0.4)], [['.selected:hover', 'background-color'], lighten_color_hex(self.windows_color, 0.45)], @@ -1024,17 +1066,9 @@ def apply_global_theme(self): for step in steps: select_styles = select_styles.replace(step[0], StyleReplaceMode.ALL, step[1], output_json=False) + logger.debug('Apply theme') app.setStyleSheet(select_styles.css_text) # 全局应用 - self.refresh() - -class SettingValue(SettingValue): - def get(self, value): - default_value = default_settings.get(value, None) - if isinstance(default_value, str): - if default_value.startswith('!var '): # 需要加载变量 - var_name = default_value[5:] - default_value = eval(var_name) - return settings.get(value, default_value) + self.need_refresh = True class MainWindow(UMainWindow): def __init__(self): @@ -1189,6 +1223,7 @@ def create_menu_bar(self): # 设置菜单 settings_menu = menu_bar.addMenu(get_lang('04')) settings_action = settings_menu.addAction(get_lang('05')) + attr_action = settings_menu.addAction(get_lang('8c')) # 更新菜单 update_menu = menu_bar.addMenu(get_lang('06')) @@ -1236,7 +1271,7 @@ def create_menu_bar(self): # not_official_extension_menu.addAction(get_lang('98')).triggered.connect(self.show_import_extension_mode) # 管理扩展菜单 # not_official_extension_menu.addAction(get_lang('92')).triggered.connect(self.show_manage_not_official_extension) # 管理扩展菜单 - # 宏菜单 + # # 宏菜单 # macro_menu = menu_bar.addMenu(get_lang('99')) # run_marco_menu = macro_menu.addMenu(get_lang('9d')) @@ -1254,6 +1289,7 @@ def create_menu_bar(self): settings_action.triggered.connect(self.show_setting) exit_action.triggered.connect(app.quit) create_issue_action.triggered.connect(lambda: open_url(setting_value.feedback)) + attr_action.triggered.connect(self.show_attr) def open_doc(self, *, path: str=''): '''打开文档''' @@ -1355,6 +1391,11 @@ def show_about(self): logger.info('Opening about window') about_window.exec() + def show_attr(self): + '''显示属性窗口''' + logger.info('Opening attribute window') + click_attr_window.show() + def show_update_log(self): '''显示更新日志''' logger.info('Opening update log') @@ -1675,7 +1716,7 @@ def init_ui(self): self.cache_dir_list = {'logs'} # 缓存文件路径的列表 self.cache_file_list = {'update.json'} # 缓存文件列表 - self.all_checkbox = UCheckBox('') + self.all_checkbox = UCheckBox(get_lang('db')) self.all_checkbox.setTristate(True) self.locked_checkbox = True # 临时切换 self.all_checkbox.setCheckState(Qt.PartiallyChecked) # 初始状态为部分选中 @@ -1989,8 +2030,6 @@ def __init__(self): self.init_ui() self.down_thread = None # 下载线程 - new_color_bar(self) - def init_ui(self): # 创建面板 logger.debug('Create layout') @@ -2157,161 +2196,35 @@ def on_open_update_log(self): # 打开更新日志 update_window.on_open_update_log() -class FastSetClickWindow(UDialog): - def __init__(self): - logger.debug('Initizalizing fast set click window') - - super().__init__() - self.setWindowTitle(get_lang('75')) - self.setGeometry(100, 100, 475, 125) - self.setWindowFlags( - Qt.Window | Qt.WindowMinimizeButtonHint | Qt.WindowCloseButtonHint - ) # 设置窗口属性 - self.setFixedSize(self.width(), self.height()) # 固定窗口大小 - - logger.debug('Initizalizing value') - self.total_run_time = 0 # 总运行时间 - - logger.debug('Initizalizing ui') - - if dev_flags.get('decoupling', False): - self.init_ui() - else: - self.init_ui_old() - - def init_ui_old(self): - # 创建主控件和布局 - central_layout = QVBoxLayout() - - # 单位输入框 - unit_layout = UnitInputLayout() - - self.input_delay = QLineEdit() - self.input_delay.setFixedWidth(300) - self.input_delay.setFixedHeight(30) - - self.delay_combo = QComboBox() - self.delay_combo.addItems([get_lang('ms', source=unit_lang), get_lang('s', source=unit_lang)]) - self.delay_combo.setFixedWidth(60) - self.delay_combo.setFixedHeight(30) - - unit_layout.addUnitRow(get_lang('11'), self.input_delay, self.delay_combo) - - self.input_times = QLineEdit() - self.input_times.setFixedWidth(300) - self.input_times.setFixedHeight(30) - - self.times_combo = QComboBox() - self.times_combo.addItems([get_lang('66'), get_lang('2a'), get_lang('2b')]) - - unit_layout.addUnitRow(get_lang('5c'), self.input_times, self.times_combo) - - # 总连点时长提示 - self.total_time_label = QLabel(main_window.total_time_label.text()) - self.total_time_label.setAlignment(Qt.AlignHCenter) - set_style(self.total_time_label, StyleClass.big_16) - - # 创建布局 - logger.debug('Create button layout') - - central_layout.addLayout(unit_layout) - central_layout.addWidget(self.total_time_label) - self.setLayout(central_layout) - - # 按钮信号连接 - logger.debug('Signal connection') - - # 双向同步 - # 主窗口同步 - main_window.input_delay.textChanged.connect(lambda: self.sync_input(QLineEdit.text, QLineEdit.setText, main_window.input_delay, self.input_delay)) - main_window.input_times.textChanged.connect(lambda: self.sync_input(QLineEdit.text, QLineEdit.setText, main_window.input_times, self.input_times)) - main_window.delay_combo.currentIndexChanged.connect(lambda: self.sync_input(QComboBox.currentIndex, QComboBox.setCurrentIndex, main_window.delay_combo, self.delay_combo)) - main_window.times_combo.currentIndexChanged.connect(lambda: self.sync_input(QComboBox.currentIndex, QComboBox.setCurrentIndex, main_window.times_combo, self.times_combo)) - main_window.total_time_label.textChanged.connect(lambda: self.sync_input(QLabel.text, QLabel.setText, main_window.total_time_label, self.total_time_label)) - - # 本窗口同步 - self.input_delay.textChanged.connect(lambda: self.sync_input(QLineEdit.text, QLineEdit.setText, self.input_delay, main_window.input_delay)) - self.input_times.textChanged.connect(lambda: self.sync_input(QLineEdit.text, QLineEdit.setText, self.input_times, main_window.input_times)) - self.delay_combo.currentIndexChanged.connect(lambda: self.sync_input(QComboBox.currentIndex, QComboBox.setCurrentIndex, self.delay_combo, main_window.delay_combo)) - self.times_combo.currentIndexChanged.connect(lambda: self.sync_input(QComboBox.currentIndex, QComboBox.setCurrentIndex, self.times_combo, main_window.times_combo)) - - logger.debug('Initizalizing fast set click window successful.') - - def init_ui(self): - self.ui = UIWindow(uiml.compile_ui_file(get_resource_path('ui', 'fastClick.gui'))) - - self.setLayout(self.ui.show()) - - # 主窗口同步 主窗口独立文件没有完成 - main_window.input_delay.textChanged.connect(lambda: self.sync_input(QLineEdit.text, QLineEdit.setText, main_window.input_delay, self.ui.find_widget('central_layout.unit_layout.input_delay'))) - main_window.input_times.textChanged.connect(lambda: self.sync_input(QLineEdit.text, QLineEdit.setText, main_window.input_times, self.ui.find_widget('central_layout.unit_layout.input_times'))) - main_window.delay_combo.currentIndexChanged.connect(lambda: self.sync_input(QComboBox.currentIndex, QComboBox.setCurrentIndex, main_window.delay_combo, self.ui.find_widget('central_layout.unit_layout.delay_combo'))) - main_window.times_combo.currentIndexChanged.connect(lambda: self.sync_input(QComboBox.currentIndex, QComboBox.setCurrentIndex, main_window.times_combo, self.ui.find_widget('central_layout.unit_layout.times_combo'))) - main_window.total_time_label.textChanged.connect(lambda: self.sync_input(QLabel.text, QLabel.setText, main_window.total_time_label, self.ui.find_widget('central_layout.total_time_label'))) - - def sync_input(self, get_handle, set_handle, source, dest): - '''同步输入框''' - set_handle(dest, get_handle(source)) - class ClickAttrWindow(UDialog): def __init__(self): logger.debug('Initizalizing click attribute window') super().__init__() - self.setWindowTitle(get_lang('8c')) + self.setWindowTitle(filter_hotkey(get_lang('8c'))) # 定义变量 self.timer = QTimer(self) - self.timer.timeout.connect(self.update_attr if dev_flags.get('decoupling', False) else self.update_attr_old) + self.timer.timeout.connect(self.update_attr) self.timer.start(setting_value.soft_delay) - if dev_flags.get('decoupling', False): - self.init_ui() - else: - self.init_ui_old() + self.init_ui() def init_ui(self): - self.layout_list = UIWindow(uiml.compile_ui_file(get_resource_path('ui', 'clickattr.gui'))) + self.layout_list = UIWindow(uiml.compile_ui_file(get_resource_path('ui', 'clickattr.gui'), additional=self.addtional_local_value)) self.setLayout(self.layout_list.show()) logger.debug('Initizalizing click attribute window successful.') - - def init_ui_old(self): - # 创建主布局 - central_layout = QVBoxLayout() - - # 内容 - self.left_clicked = QLabel(f'{get_lang('0d')}:') - self.right_clicked = QLabel(f'{get_lang('0e')}:') - self.click_delay = QLabel(f'{get_lang('78')}:') - self.click_times = QLabel(f'{get_lang('5c')}:') - self.paused = QLabel(f'{get_lang('0f')}:') - self.stopped = QLabel(f'{get_lang('0e')}:') - self.total_run_time = QLabel(f'{get_lang('2c')}:') - - # 底边栏 - bottom_layout = QHBoxLayout() - ok_button = QPushButton(get_lang('1e')) - set_style(ok_button, StyleClass.selected) - ok_button.clicked.connect(self.close) - - # 布局 - bottom_layout.addStretch(1) - bottom_layout.addWidget(ok_button) - - central_layout.addWidget(self.left_clicked) - central_layout.addWidget(self.right_clicked) - central_layout.addWidget(self.click_delay) - central_layout.addWidget(self.click_times) - central_layout.addWidget(self.paused) - central_layout.addWidget(self.stopped) - central_layout.addWidget(self.total_run_time) - central_layout.addLayout(bottom_layout) - - self.setLayout(central_layout) def update_attr(self): '''更新属性''' logger.debug('update attribute') + if is_error: + _delay_num = get_lang('14') + _time_num = get_lang('14') + else: + _delay_num = get_unit_text(delay_num) + _time_num = get_lang('2b') if is_inf else str(time_num) + get_lang('66') + left_clicked = self.layout_list.find_widget('central_layout.left_clicked') right_clicked = self.layout_list.find_widget('central_layout.right_clicked') click_delay = self.layout_list.find_widget('central_layout.click_delay') @@ -2319,38 +2232,14 @@ def update_attr(self): paused = self.layout_list.find_widget('central_layout.paused') stopped = self.layout_list.find_widget('central_layout.stopped') total_run_time = self.layout_list.find_widget('central_layout.total_run_time') - + left_clicked.setText(f'{get_lang('0c')}: {get_lang('7b') if clicker.left_clicked else get_lang('7c')}') right_clicked.setText(f'{get_lang('0d')}: {get_lang('7b') if clicker.right_clicked else get_lang('7c')}') - click_delay.setText(f'{get_lang('78')}: {delay_num}{get_lang('ms', source=unit_lang)}') - click_times.setText(f'{get_lang('5c')}: {get_lang('2b') if is_inf else time_num}') + click_delay.setText(f'{get_lang('78')}: {_delay_num}') + click_times.setText(f'{get_lang('5c')}: {_time_num}') paused.setText(f'{get_lang('0f')}: {get_lang('79') if clicker.paused else get_lang('7a')}') stopped.setText(f'{get_lang('0e')}: {get_lang('79') if not clicker.running else get_lang('7a')}') - try: - if is_inf: - total_run_time.setText(f'{get_lang('2c')}: {get_lang('2b')}') - else: - total_run_time.setText(f'{get_lang('2c')}: {main_window.total_run_time[0]:.2f}{main_window.total_run_time[1]}') - except TypeError: - value = get_unit_value(main_window.total_run_time) - total_run_time.setText(f'{get_lang('2c')}: {value[0]:.2f}{value[1]}') - - def update_attr_old(self): - logger.debug('update attribute') - self.left_clicked.setText(f'{get_lang('0c')}: {get_lang('7b') if clicker.left_clicked else get_lang('7c')}') - self.right_clicked.setText(f'{get_lang('0d')}: {get_lang('7b') if clicker.right_clicked else get_lang('7c')}') - self.click_delay.setText(f'{get_lang('78')}: {delay_num}{get_lang('ms', source=unit_lang)}') - self.click_times.setText(f'{get_lang('5c')}: {get_lang('2b') if is_inf else time_num}') - self.paused.setText(f'{get_lang('0f')}: {get_lang('79') if clicker.paused else get_lang('7a')}') - self.stopped.setText(f'{get_lang('0e')}: {get_lang('79') if not clicker.running else get_lang('7a')}') - try: - if is_inf: - self.total_run_time.setText(f'{get_lang('2c')}: {get_lang('2b')}') - else: - self.total_run_time.setText(f'{get_lang('2c')}: {main_window.total_run_time[0]:.2f}{main_window.total_run_time[1]}') - except TypeError: - value = get_unit_value(main_window.total_run_time) - self.total_run_time.setText(f'{get_lang('2c')}: {value[0]:.2f}{value[1]}') + total_run_time.setText(main_window.total_time_label.text()) class SettingWindow(SelectUI, UMainWindow): click_setting_changed = Signal() @@ -2376,6 +2265,9 @@ def __init__(self, values:dict | None = None): self.page_choice_buttons = [get_lang('42'), get_lang('a6'), get_lang('43'), get_lang('44'), get_lang('69'), filter_hotkey(get_lang('5f')), get_lang('d3')] self.ui_file_name = ['general.gui', 'style.gui', 'clicker.gui', 'updater.gui', 'hotkey.gui', 'document.gui', 'notify.gui', 'flags.gui'] + # 主程序 + self.app = get_application_instance() + self.create_setting_page_value() self.last_page = None @@ -2383,33 +2275,13 @@ def __init__(self, values:dict | None = None): self.values = {} if values is None else values self.code_list = {} - if dev_flags.get('decoupling', False): - self.init_ui() - else: - self.init_ui_old() + self.init_ui() self.check_values() # 检查设置值 # 连接信号 clicker.started.connect(self.on_clicker_started) logger.debug('Initizalizing setting window successful.') - - def init_ui_old(self): - '''创建设置界面''' - self.draw_page_choice() - self.init_right_pages_old() - - def init_right_pages_old(self): - '''初始化右侧设置页面''' - # 创建堆叠窗口部件 - self.stacked_widget = QStackedWidget() - - # 为每个左侧选项创建一个对应的右侧页面 - for i, page_title in enumerate(self.page_choice_buttons): - page = self.create_setting_page_old(page_title) - self.stacked_widget.addWidget(page) - self.pages.append(page) - # 将堆叠窗口部件设置为右侧滚动区域的内容 self.right_scroll.setWidget(self.stacked_widget) @@ -2440,10 +2312,9 @@ def check_values(self): def get_code(self, id) -> UIWindow: return self.code_list[id+'.gui'] - def create_setting_page_old(self, title): + def create_setting_page(self, title): logger.info(f'Loading setting page: {title}') page = QWidget() - layout = QVBoxLayout(page) def set_content_label(text): logger.debug(f'Set content label: {text}') @@ -2452,239 +2323,67 @@ def set_content_label(text): def create_horizontal_line(): logger.debug('Create horizontal line') line = UFrame() - line.setFrameShape(UFrame.Shape.HLine) # 水平线 return line def parse_hotkey(input: UHotkeyLineEdit): return input.text().split('+') - - # 标题标签 - title_label = QLabel(title) - set_style(title_label, StyleClass.big_24) - - # 内容标签 - content_label = QLabel(get_lang('7d')) - set_style(content_label, StyleClass.dest) - # 布局 - layout.addWidget(title_label) - layout.addWidget(content_label) - layout.addWidget(create_horizontal_line()) + self.addtional_local_value.update({'title': title}) + if title in [self.page_general, self.page_style, self.page_click]: + layout_code = UIWindow(uiml.compile_ui_file(get_resource_path('ui', 'settings', self.ui_file_name[self.page_choice_buttons.index(title)]), additional=self.addtional_local_value)) + self.code_list[self.ui_file_name[self.page_choice_buttons.index(title)]] = layout_code + + layout = layout_code.show() + page.setLayout(layout) + else: + layout = QVBoxLayout(page) + # 标题标签 + title_label = QLabel(title) + set_style(title_label, StyleClass.big_24) - # 主程序 - self.app = get_application_instance() + # 内容标签 + content_label = QLabel(get_lang('7d')) + set_style(content_label, StyleClass.dest) + + # 布局 + layout.addWidget(title_label) + layout.addWidget(content_label) + layout.addWidget(create_horizontal_line()) # 添加一些示例设置控件 match title: case self.page_general: - set_content_label(get_lang('7f')) - # 选择语言 - lang_choice_layout = QHBoxLayout() # 语言选择布局 - self.lang_choice = QComboBox() - self.lang_choice.addItems([get_lang('c4')] + [i['lang_name'] for i in langs]) - self.lang_choice.setCurrentIndex(setting_value.select_lang + 1) - # 布局 - lang_choice_layout.addWidget(QLabel(f'{get_lang('45')}{get_lang('b5')}:')) # 选择语言提示 - lang_choice_layout.addWidget(self.lang_choice) - lang_choice_layout.addStretch(1) - - # 显示托盘图标 - tray_layout = QHBoxLayout() # 窗口风格布局 - tray = UCheckBox(get_lang('80')) - tray.setChecked(setting_value.show_tray_icon) - - tray_layout.addWidget(tray) - tray_layout.addStretch(1) - - # 开机自启动 - start_layout = QHBoxLayout() # 开机自启动布局 - self.start_checkbox = UCheckBox(get_lang('b6')) - self.start_checkbox.setChecked(auto_start_manager.auto_start) - - start_layout.addWidget(self.start_checkbox) - start_layout.addStretch(1) - - auto_start_manager.updated.connect(lambda enb: self.start_checkbox.setChecked(enb)) - self.start_checkbox.checkStateChanged.connect(self.on_auto_start_changed) - - # 重置开机自启动 - repair_start_layout = QHBoxLayout() # 重置开机自启动布局 - repair_start_button = QPushButton(get_lang('20')) + auto_start_manager.updated.connect(lambda enb: self.get_code('general').find_widget('general.start_layout.start_checkbox').setChecked(enb)) + self.get_code('general').find_widget('general.delay_layout.delay_label').setText(f'{get_lang('b0')}:{setting_value.soft_delay}{get_lang("ms", source=unit_lang)}') + case self.page_click: + self.default_delay: QLineEdit = self.get_code('clicker').find_widget('clicker.delay_layout.delay_input_layout.delay_input') + self.delay_combo: QComboBox = self.get_code('clicker').find_widget('clicker.delay_layout.delay_input_layout.delay_combo') + self.use_default_delay: QCheckBox = self.get_code('clicker').find_widget('clicker.delay_layout.error_use_default_delay') + if not self.default_delay.text(): + self.use_default_delay.setEnabled(False) + self.default_time: QLineEdit = self.get_code('clicker').find_widget('clicker.times_layout.times_input_layout.times_input') + self.times_combo: QComboBox = self.get_code('clicker').find_widget('clicker.times_layout.times_input_layout.times_combo') + self.use_default_times: QCheckBox = self.get_code('clicker').find_widget('clicker.times_layout.error_use_default_times') + if not self.default_time.text(): + self.use_default_times.setEnabled(False) + self.total_time_label: QLabel = self.get_code('clicker').find_widget('clicker.total_time_label') + if dev_flags.get('new_settings', False): + self.modify_using_default_input = self.get_code('clicker').find_widget('clicker.attributes_layout.modify_using_default_input') + self.modify_using_default_combo = self.get_code('clicker').find_widget('clicker.attributes_layout.modify_using_default_combo') + case self.page_update: + set_content_label(get_lang('87')) + # 选择更新检查提示 + self.enable_update = UCheckBox(get_lang('48')) # 开启更新 + self.enable_update.setChecked(setting_value.update_enabled) - repair_tip = QLabel(get_lang('d1')) - set_style(repair_tip, StyleClass.d_11) + update_disable_text = QLabel(get_lang('d0')) # 更新禁止提示 + set_style(update_disable_text, StyleClass.d_11) - repair_start_layout.addWidget(repair_start_button) - repair_start_layout.addWidget(repair_tip) - repair_start_layout.addStretch(1) + self.update_notify = UCheckBox(get_lang('4a')) # 更新提示 + self.update_notify.setChecked(setting_value.update_notify) - # 延迟 - soft_delay_layout = QHBoxLayout() # 颜色延迟布局 - soft_delay_setting = setting_value.soft_delay - - soft_delay = QSlider(Qt.Horizontal) - soft_delay.setMinimum(5) - soft_delay.setMaximum(100) - soft_delay.setValue(soft_delay_setting // 10) - soft_delay.setTickPosition(QSlider.TicksBelow) - soft_delay.setTickInterval(10) - soft_delay.setFixedWidth(200) - - delay_tip_label = QLabel(get_lang("8a")) - set_style(delay_tip_label, StyleClass.d_11) - - # 布局 - soft_delay_layout.addWidget(QLabel(f'{get_lang('b0')}\n{get_lang('b5')}:')) - soft_delay_layout.addWidget(soft_delay) - soft_delay_layout.addStretch(1) - - delay_layout_text = QLabel(f'{get_lang('b0')}:{soft_delay_setting}{get_lang("ms", source=unit_lang)}') - set_style(delay_layout_text, StyleClass.big_16) - - # 反馈路径 - feedback_layout = QHBoxLayout() # 反馈路径布局 - - feedback_input = QLineEdit() - feedback_input.setText(setting_value.feedback) - - repair_feedback_link_button = QPushButton(get_lang('20')) - - # 布局 - feedback_layout.addWidget(QLabel(get_lang('c3')), 1) - feedback_layout.addWidget(feedback_input, 6) - feedback_layout.addWidget(repair_feedback_link_button, 1) - - # 重置所有设置 - repair_layout = QHBoxLayout() # 重置布局 - self.repair_button = QPushButton(get_lang('5e')) - - repair_layout.addWidget(self.repair_button) - repair_layout.addStretch(1) - - # 无实验项时自动隐藏实验室 - hide_flags_checkbox = UCheckBox(get_lang('d5')) - hide_flags_checkbox.setChecked(setting_value.hide_flags) - - # 布局 - layout.addLayout(lang_choice_layout) - layout.addLayout(tray_layout) - layout.addWidget(create_horizontal_line()) - layout.addLayout(start_layout) - - layout.addLayout(repair_start_layout) - if dev_flags.get('new_settings', False): - layout.addWidget(create_horizontal_line()) - layout.addLayout(feedback_layout) - layout.addWidget(create_horizontal_line()) - layout.addLayout(soft_delay_layout) - layout.addWidget(delay_layout_text) - layout.addWidget(delay_tip_label) - layout.addWidget(create_horizontal_line()) - if dev_flags.get('new_settings', False): - layout.addWidget(hide_flags_checkbox) - layout.addWidget(create_horizontal_line()) - layout.addLayout(repair_layout) - - # 绑定事件 - self.lang_choice.currentIndexChanged.connect(lambda: self.on_need_restart_setting_changed_old(lambda: self.lang_choice.currentIndex() - 1, SettingText.select_lang)) - tray.checkStateChanged.connect(lambda: self.on_setting_changed(tray.isChecked, SettingText.show_tray_icon)) - tray.checkStateChanged.connect(lambda: self.app.setQuitOnLastWindowClosed(not tray.isChecked())) # 关闭窗口时不退出应用 - soft_delay.valueChanged.connect(lambda: self.on_setting_changed(lambda: soft_delay.value() * 10 if soft_delay.value() > 0 else 1, SettingText.soft_delay)) - soft_delay.valueChanged.connect(lambda: delay_layout_text.setText(f'{get_lang('b0')}: {soft_delay.value() * 10 if soft_delay.value() > 0 else 1}{get_lang("ms", source=unit_lang)}')) - self.repair_button.clicked.connect(self.repair_all_settings) - feedback_input.textChanged.connect(lambda: self.on_setting_changed(feedback_input.text, SettingText.feedback)) - repair_feedback_link_button.clicked.connect(lambda: self.repair_settings(SettingText.feedback)) - repair_start_button.clicked.connect(self.repair_auto_start) - hide_flags_checkbox.checkStateChanged.connect(lambda: self.on_setting_changed(hide_flags_checkbox.isChecked, SettingText.hide_flags)) - hide_flags_checkbox.checkStateChanged.connect(lambda: self.restart_window()) - case self.page_click: - set_content_label(get_lang('84')) - # 选择默认连点器延迟 - layout_delay = QVBoxLayout() # 延迟布局 - unit_delay_layout = QHBoxLayout() # 窗口风格布局 - self.default_delay = QLineEdit() - self.default_delay.setText(setting_value.click_delay) - self.delay_combo = QComboBox() - self.delay_combo.addItems([get_lang('ms', source=unit_lang), get_lang('s', source=unit_lang)]) - self.delay_combo.setCurrentIndex(setting_value.delay_unit) - - unit_delay_layout.addWidget(QLabel(get_lang('46') + ': ')) - unit_delay_layout.addWidget(self.default_delay) - unit_delay_layout.addWidget(self.delay_combo) - unit_delay_layout.addStretch(1) - - # 连点出错时使用默认值 - use_default_delay = UCheckBox(get_lang('47')) - use_default_delay.setChecked(setting_value.delay_error_use_default) - if not self.default_delay.text(): - use_default_delay.setEnabled(False) - - # 布局 - layout_delay.addLayout(unit_delay_layout) - layout_delay.addWidget(use_default_delay) - layout_delay.addWidget(create_horizontal_line()) - layout_delay.addStretch(1) - - # 连点器默认点击次数 - layout_time = QVBoxLayout() # 次数布局 - unit_time_layout = QHBoxLayout() # 窗口风格布局 - self.default_time = QLineEdit() - self.default_time.setText(str(setting_value.click_times)) - self.times_combo = QComboBox() - self.times_combo.addItems([get_lang('66'), get_lang('2a'), get_lang('2b')]) - self.times_combo.setCurrentIndex(setting_value.times_unit) - - unit_time_layout.addWidget(QLabel(get_lang('85') + ': ')) - unit_time_layout.addWidget(self.default_time) - unit_time_layout.addWidget(self.times_combo) - unit_time_layout.addStretch(1) - - # 连点出错时使用默认值 - use_default_time = UCheckBox(get_lang('86')) - use_default_time.setChecked(setting_value.times_error_use_default) - if not self.default_time.text(): - use_default_time.setEnabled(False) - self.total_time_label = QLabel(f'{get_lang('2c')}: {get_lang('61')}') - self.total_time_label.setAlignment(Qt.AlignHCenter) - set_style(self.total_time_label, StyleClass.big_16) - - # 布局 - layout_time.addLayout(unit_time_layout) - layout_time.addWidget(use_default_time) - layout_time.addWidget(create_horizontal_line()) - layout_time.addStretch(1) - - # 布局 - layout.addLayout(layout_delay) - layout.addLayout(layout_time) - layout.addWidget(self.total_time_label) - layout.addStretch(1) - - # 连接信号 - self.default_delay.textChanged.connect(lambda: self.on_default_input_changed(self.default_delay, SettingText.click_delay, use_default_delay)) - self.default_delay.textChanged.connect(lambda: on_input_change(type=InputChange.setting_window)) - use_default_delay.checkStateChanged.connect(lambda: self.on_setting_changed(use_default_delay.isChecked, SettingText.delay_error_use_default)) - self.default_time.textChanged.connect(lambda: self.on_default_input_changed(self.default_time, SettingText.click_times, use_default_time)) - self.default_time.textChanged.connect(lambda: on_input_change(type=InputChange.setting_window)) - use_default_time.checkStateChanged.connect(lambda: self.on_setting_changed(use_default_time.isChecked, SettingText.times_error_use_default)) - self.delay_combo.currentIndexChanged.connect(lambda: self.on_setting_changed(self.delay_combo.currentIndex, SettingText.delay_unit)) - self.delay_combo.currentIndexChanged.connect(lambda: on_input_change(type=InputChange.setting_window)) - self.times_combo.currentIndexChanged.connect(lambda: self.on_setting_changed(self.times_combo.currentIndex, SettingText.times_unit)) - self.times_combo.currentIndexChanged.connect(lambda: on_input_change(type=InputChange.setting_window)) - case self.page_update: - set_content_label(get_lang('87')) - # 选择更新检查提示 - self.enable_update = UCheckBox(get_lang('48')) # 开启更新 - self.enable_update.setChecked(setting_value.update_enabled) - - update_disable_text = QLabel(get_lang('d0')) # 更新禁止提示 - set_style(update_disable_text, StyleClass.d_11) - - self.update_notify = UCheckBox(get_lang('4a')) # 更新提示 - self.update_notify.setChecked(setting_value.update_notify) - - self.quiet_install = UCheckBox(get_lang('49')) # 静默安装 - self.quiet_install.setChecked(setting_value.quiet_update) + self.quiet_install = UCheckBox(get_lang('49')) # 静默安装 + self.quiet_install.setChecked(setting_value.quiet_update) self.update_ok = UCheckBox(get_lang('4c')) # 更新完成弹出提示 self.update_ok.setChecked(setting_value.update_ok_notify) @@ -2716,537 +2415,60 @@ def parse_hotkey(input: UHotkeyLineEdit): self.update_ok.checkStateChanged.connect(self.on_sync_ok_notice) else: self.on_enable_update(self.enable_update.isChecked()) - case self.page_style: - set_content_label(get_lang('a7')) - # 选择窗口风格 - style_layout = QHBoxLayout() # 窗口风格布局 - self.style_choice = QComboBox() - - items = list(style_indexes[select_lang]['lang_package'].values()) - - self.style_choice.addItems([get_lang('82')] + items) - self.style_choice.setCurrentIndex(setting_value.select_style) - - # 布局 - style_layout.addWidget(QLabel(get_lang('81'))) # 选择窗口风格提示 - style_layout.addWidget(self.style_choice) - style_layout.addStretch(1) - - style_use_windows_layout = QHBoxLayout() # 颜色使用windows按钮布局 - style_choice_use_windows = UCheckBox(get_lang('a8')) - tip_label = QLabel(get_lang('b4')) - set_style(tip_label, StyleClass.d_11) - style_choice_use_windows.setChecked(setting_value.use_windows_color) - - # 布局 - style_use_windows_layout.addWidget(style_choice_use_windows) - style_use_windows_layout.addStretch(1) - - theme_layout = QHBoxLayout() # 主题布局 - theme_tip_label = QLabel(get_lang('4b')) - set_style(theme_tip_label, StyleClass.d_11) - theme_combo = QComboBox() - theme_combo.addItems(QStyleFactory.keys()) - theme_combo.setCurrentText(setting_value.theme) - - # 布局 - theme_layout.addWidget(QLabel(get_lang('23'))) - theme_layout.addWidget(theme_combo) - theme_layout.addStretch(1) - - # 布局 - layout.addLayout(style_layout) - layout.addWidget(create_horizontal_line()) - layout.addLayout(style_use_windows_layout) - layout.addWidget(tip_label) - layout.addWidget(create_horizontal_line()) - layout.addLayout(theme_layout) - layout.addWidget(theme_tip_label) - layout.addWidget(create_horizontal_line()) - - # 连接信号 - self.style_choice.currentIndexChanged.connect(lambda: self.on_setting_changed(self.style_choice.currentIndex, SettingText.select_style)) - style_choice_use_windows.checkStateChanged.connect(lambda: self.on_setting_changed(style_choice_use_windows.isChecked, SettingText.use_windows_color)) - theme_combo.currentIndexChanged.connect(lambda: self.on_setting_changed(theme_combo.currentText, SettingText.theme)) - theme_combo.currentIndexChanged.connect(lambda: refresh.run()) - theme_combo.currentIndexChanged.connect(lambda: self.app.setStyle(theme_combo.currentText())) case self.page_hotkey: set_content_label(get_lang('21')) self.hotkey_enabled = UCheckBox(get_lang('c9')) # 热键启用 self.hotkey_enabled.setChecked(setting_value.hotkey_enabled) - - # 左键连点 - self.left_click_layout = QHBoxLayout() - self.left_click_input = UHotkeyLineEdit() # 左键连点输入框 - self.left_click_input.setText(format_keys(setting_value.left_click_hotkey)) - self.left_repair_button = QPushButton(get_lang('20')) # 还原默认设置按钮 - - # 布局 - self.left_click_layout.addWidget(QLabel(f'{get_lang('0c')}: '), 1) # 左键连点提示 - self.left_click_layout.addWidget(self.left_click_input, 6) - self.left_click_layout.addWidget(self.left_repair_button, 1) - self.left_click_layout.addStretch() - - # 右键连点 - self.right_click_layout = QHBoxLayout() # 右键连点布局 - self.right_click_input = UHotkeyLineEdit() # 右键连点输入框 - self.right_repair_button = QPushButton(get_lang('20')) # 还原默认设置按钮 - - self.right_click_input.setText(format_keys(setting_value.right_click_hotkey)) - - # 布局 - self.right_click_layout.addWidget(QLabel(f'{get_lang('0d')}: '), 1) # 右键连点提示 - self.right_click_layout.addWidget(self.right_click_input, 6) - self.right_click_layout.addWidget(self.right_repair_button, 1) - self.right_click_layout.addStretch() - - # 暂停/重启连点 - self.pause_click_layout = QHBoxLayout() # 暂停/重启连点布局 - self.pause_click_input = UHotkeyLineEdit() # 暂停/重启连点输入框 - self.pause_click_input.setText(format_keys(setting_value.pause_click_hotkey)) - self.pause_repair_button = QPushButton(get_lang('20')) # 还原默认设置按钮 - - # 布局 - self.pause_click_layout.addWidget(QLabel(f'{get_lang('6b')}: '), 1) # 暂停/重启连点提示 - self.pause_click_layout.addWidget(self.pause_click_input, 6) - self.pause_click_layout.addWidget(self.pause_repair_button, 1) - self.pause_click_layout.addStretch() - - # 停止连点 - self.stop_click_layout = QHBoxLayout() # 停止连点布局 - self.stop_click_input = UHotkeyLineEdit() # 停止连点输入框 - self.stop_click_input.setText(format_keys(setting_value.stop_click_hotkey)) - self.stop_repair_button = QPushButton(get_lang('20')) # 还原默认设置按钮 - - # 布局 - self.stop_click_layout.addWidget(QLabel(f'{get_lang('6c')}: '), 1) # 停止连点提示 - self.stop_click_layout.addWidget(self.stop_click_input, 6) - self.stop_click_layout.addWidget(self.stop_repair_button, 1) - self.stop_click_layout.addStretch() - - # 连点属性 - self.click_attr_layout = QHBoxLayout() # 连点属性布局 - self.click_attr_input = UHotkeyLineEdit() # 连点属性输入框 - self.click_attr_input.setText(format_keys(setting_value.click_attr_hotkey)) - self.click_attr_button = QPushButton(get_lang('20')) # 还原默认设置按钮 - - # 布局 - self.click_attr_layout.addWidget(QLabel(f'{get_lang('8c')}: '), 1) # 连点属性提示 - self.click_attr_layout.addWidget(self.click_attr_input, 6) - self.click_attr_layout.addWidget(self.click_attr_button, 1) - self.click_attr_layout.addStretch() - - # 快速连点 - self.fast_click_layout = QHBoxLayout() # 快速连点布局 - self.fast_click_input = UHotkeyLineEdit() # 快速连点输入框 - self.fast_click_input.setText(format_keys(setting_value.fast_click_hotkey)) - self.fast_click_button = QPushButton(get_lang('20')) # 还原默认设置按钮 - - # 布局 - self.fast_click_layout.addWidget(QLabel(f'{get_lang('75')}: '), 1) # 快速连点提示 - self.fast_click_layout.addWidget(self.fast_click_input, 6) - self.fast_click_layout.addWidget(self.fast_click_button, 1) - self.fast_click_layout.addStretch() - - # 主窗口 - self.main_window_layout = QHBoxLayout() # 主窗口布局 - self.main_window_input = UHotkeyLineEdit() # 主窗口输入框 - self.main_window_input.setText(format_keys(setting_value.main_window_hotkey)) - self.main_window_button = QPushButton(get_lang('20')) # 还原默认设置按钮 - # 布局 - self.main_window_layout.addWidget(QLabel(f'{get_lang('76')}: '), 1) # 主窗口提示 - self.main_window_layout.addWidget(self.main_window_input, 6) - self.main_window_layout.addWidget(self.main_window_button, 1) - self.main_window_layout.addStretch() + self.hotkeys_widget_list = [] - # 布局 - if dev_flags.get('new_settings', False): - layout.addWidget(self.hotkey_enabled) - layout.addLayout(self.left_click_layout) - layout.addLayout(self.right_click_layout) - layout.addLayout(self.pause_click_layout) - layout.addLayout(self.stop_click_layout) - layout.addLayout(self.click_attr_layout) - layout.addLayout(self.fast_click_layout) - layout.addLayout(self.main_window_layout) - - # 连接信号 - self.left_click_input.textChanged.connect(lambda: self.on_setting_changed(lambda: parse_hotkey(self.left_click_input), SettingText.left_click_hotkey)) - self.right_click_input.textChanged.connect(lambda: self.on_setting_changed(lambda: parse_hotkey(self.right_click_input), SettingText.right_click_hotkey)) - self.pause_click_input.textChanged.connect(lambda: self.on_setting_changed(lambda: parse_hotkey(self.pause_click_input), SettingText.pause_click_hotkey)) - self.stop_click_input.textChanged.connect(lambda: self.on_setting_changed(lambda: parse_hotkey(self.stop_click_input), SettingText.stop_click_hotkey)) - self.click_attr_input.textChanged.connect(lambda: self.on_setting_changed(lambda: parse_hotkey(self.click_attr_input), SettingText.click_attr_hotkey)) - self.fast_click_input.textChanged.connect(lambda: self.on_setting_changed(lambda: parse_hotkey(self.fast_click_input), SettingText.fast_click_hotkey)) - self.main_window_input.textChanged.connect(lambda: self.on_setting_changed(lambda: parse_hotkey(self.main_window_input), SettingText.main_window_hotkey)) - - self.left_repair_button.clicked.connect(lambda: self.repair_settings(SettingText.left_click_hotkey)) - self.right_repair_button.clicked.connect(lambda: self.repair_settings(SettingText.right_click_hotkey)) - self.pause_repair_button.clicked.connect(lambda: self.repair_settings(SettingText.pause_click_hotkey)) - self.stop_repair_button.clicked.connect(lambda: self.repair_settings(SettingText.stop_click_hotkey)) - self.click_attr_button.clicked.connect(lambda: self.repair_settings(SettingText.click_attr_hotkey)) - self.fast_click_button.clicked.connect(lambda: self.repair_settings(SettingText.fast_click_hotkey)) - self.main_window_button.clicked.connect(lambda: self.repair_settings(SettingText.main_window_hotkey)) - self.hotkey_enabled.checkStateChanged.connect(self.on_enable_hotkey_changed) self.on_enable_hotkey_changed(self.hotkey_enabled.isChecked() if dev_flags.get('new_settings', False) else True) - case self.page_doc: - set_content_label(get_lang('ca')) - - default_doc_layout = QHBoxLayout() # 默认打开文档布局 - - default_doc_link = QLineEdit() # 默认打开文档链接 - default_doc_link.setText(setting_value.default_doc_link) - repair_default_doc_link_button = QPushButton(get_lang('20')) # 还原默认设置按钮 - - # 布局 - default_doc_layout.addWidget(QLabel(get_lang('c2')), 1) # 默认打开文档提示 - default_doc_layout.addWidget(default_doc_link, 6) - if dev_flags.get('new_settings', False): - default_doc_layout.addWidget(repair_default_doc_link_button, 1) - default_doc_layout.addStretch() - - default_lang_layout = QHBoxLayout() # 默认文档语言布局 - lang_choice = QComboBox() # 语言选择框 - lang_choice.addItems([get_lang('45'), get_lang('c4')] + [i['lang_name'] for i in langs if i['supported']]) - lang_choice.setCurrentIndex(setting_value.lang_doc) - - # 布局 - default_lang_layout.addWidget(QLabel(get_lang('c5'))) # 默认文档语言提示 - default_lang_layout.addWidget(lang_choice) - default_lang_layout.addStretch() - - update_log_path_layout = QHBoxLayout() # 更新日志路径布局 - update_log_path_input = QLineEdit() # 更新日志路径输入框 - update_log_path_input.setText(setting_value.update_log_path) - - repair_update_log_path_button = QPushButton(get_lang('20')) # 还原默认路径按钮 - - # 布局 - update_log_path_layout.addWidget(QLabel(get_lang('c6')), 1) # 更新日志路径提示 - update_log_path_layout.addWidget(update_log_path_input, 6) - if dev_flags.get('new_settings', False): - update_log_path_layout.addWidget(repair_update_log_path_button, 1) - update_log_path_layout.addStretch() - - label = QLabel(get_lang('c7')) - # 布局 - set_style(label, StyleClass.d_11) - - layout.addLayout(default_doc_layout) - layout.addLayout(default_lang_layout) - layout.addWidget(create_horizontal_line()) - layout.addLayout(update_log_path_layout) - layout.addWidget(create_horizontal_line()) - layout.addWidget(label) - - # 链接信号 - default_doc_link.textChanged.connect(lambda: self.on_setting_changed(default_doc_link.text, SettingText.default_doc_link)) - lang_choice.currentIndexChanged.connect(lambda: self.on_setting_changed(self.lang_choice.currentIndex, SettingText.lang_doc)) - update_log_path_input.textChanged.connect(lambda: self.on_setting_changed(update_log_path_input.text, SettingText.update_log_path)) - repair_default_doc_link_button.clicked.connect(lambda: self.repair_settings(SettingText.default_doc_link)) - repair_update_log_path_button.clicked.connect(lambda: self.repair_settings(SettingText.update_log_path)) - case self.page_notify: - set_content_label(get_lang('cc')) - - # 更新提示 - self.notice_update_notify = UCheckBox(get_lang('4a')) - self.notice_update_notify.setChecked(setting_value.update_notify) - - # 更新完成提示 - self.notice_update_ok_notify = UCheckBox(get_lang('4c')) - self.notice_update_ok_notify.setChecked(setting_value.update_ok_notify) - - # 启用软件启动警告 - self.start_warning = UCheckBox(get_lang('cd')) - tip_label = QLabel(get_lang('ce')) - set_style(tip_label, StyleClass.d_11) - self.start_warning.setChecked(setting_value.show_warning) - - self.package_warning = UCheckBox(get_lang('cf')) - self.package_warning.setChecked(setting_value.show_package_warning) - - # 布局 - layout.addWidget(self.notice_update_notify) - layout.addWidget(self.notice_update_ok_notify) - layout.addWidget(create_horizontal_line()) - layout.addWidget(self.start_warning) - layout.addWidget(tip_label) - layout.addWidget(self.package_warning) - - # 连接信号 - self.notice_update_notify.checkStateChanged.connect(lambda: self.on_setting_changed(self.notice_update_notify.isChecked, SettingText.update_notify)) - self.notice_update_notify.checkStateChanged.connect(self.on_sync_notice) - self.notice_update_ok_notify.checkStateChanged.connect(lambda: self.on_setting_changed(self.notice_update_ok_notify.isChecked, SettingText.update_ok_notify)) - self.notice_update_ok_notify.checkStateChanged.connect(self.on_sync_ok_notice) - self.start_warning.checkStateChanged.connect(self.on_enable_warn) - self.package_warning.checkStateChanged.connect(lambda: self.on_setting_changed(self.package_warning.isChecked, SettingText.show_package_warning)) - - self.on_enable_update(self.enable_update.isChecked()) - self.on_warning_update(self.start_warning.isChecked()) - case self.page_flags: - set_content_label(get_lang('d4')) - - if not dev_settings: - layout.addWidget(QLabel('No dev settings found.')) - else: - for i in dev_settings: - checkbox = UCheckBox(i['name']) - if i['key'] == 'new_settings': - checkbox.checkStateChanged.connect(lambda chk,idx=i['key']:(self.save_dev_config(chk, idx),self.window_restarted.emit(),)) - else: - checkbox.checkStateChanged.connect(lambda chk,idx=i['key']:(self.save_dev_config(chk, idx))) - checkbox.setChecked(dev_flags.get(i['key'], False)) - desc = QLabel(i['desc']) - set_style(desc, StyleClass.d_11) - - layout.addWidget(checkbox) - layout.addWidget(desc) - layout.addWidget(create_horizontal_line()) - - restart_layout = QHBoxLayout() # 重启提示布局 - self.restart_button = QPushButton(get_lang('7e')) - - set_style(self.restart_button, StyleClass.selected) - self.restart_button.clicked.connect(self.restart) - - restart_layout.addStretch() - restart_layout.addWidget(self.restart_button) - - if not settings_need_restart: - self.restart_button.hide() - - layout.addLayout(restart_layout) - - # 添加弹簧,让内容靠上显示 - layout.addStretch() - - return page - - def create_setting_page(self, title): - logger.info(f'Loading setting page: {title}') - page = QWidget() - - def set_content_label(text): - logger.debug(f'Set content label: {text}') - content_label.setText(text) - - def create_horizontal_line(): - logger.debug('Create horizontal line') - line = UFrame() - return line - - def parse_hotkey(input: UHotkeyLineEdit): - return input.text().split('+') - - # 主程序 - self.app = get_application_instance() - - if title in [self.page_general, self.page_style, self.page_click]: - layout_code = UIWindow(uiml.compile_ui_file(get_resource_path('ui', 'settings', self.ui_file_name[self.page_choice_buttons.index(title)]))) - self.code_list[self.ui_file_name[self.page_choice_buttons.index(title)]] = layout_code - - layout = layout_code.show() - page.setLayout(layout) - else: - layout = QVBoxLayout(page) - # 标题标签 - title_label = QLabel(title) - set_style(title_label, StyleClass.big_24) - - # 内容标签 - content_label = QLabel(get_lang('7d')) - set_style(content_label, StyleClass.dest) - - # 布局 - layout.addWidget(title_label) - layout.addWidget(content_label) - layout.addWidget(create_horizontal_line()) - - # 添加一些示例设置控件 - match title: - case self.page_general: - auto_start_manager.updated.connect(lambda enb: self.get_code('general').find_widget('general.start_layout.start_checkbox').setChecked(enb)) - self.get_code('general').find_widget('general.delay_layout.delay_label').setText(f'{get_lang('b0')}:{setting_value.soft_delay}{get_lang("ms", source=unit_lang)}') - case self.page_click: - self.default_delay: QLineEdit = self.get_code('clicker').find_widget('clicker.delay_layout.delay_input_layout.delay_input') - self.delay_combo: QComboBox = self.get_code('clicker').find_widget('clicker.delay_layout.delay_input_layout.delay_combo') - self.use_default_delay: QCheckBox = self.get_code('clicker').find_widget('clicker.delay_layout.error_use_default_delay') - if not self.default_delay.text(): - self.use_default_delay.setEnabled(False) - self.default_time: QLineEdit = self.get_code('clicker').find_widget('clicker.times_layout.times_input_layout.times_input') - self.times_combo: QComboBox = self.get_code('clicker').find_widget('clicker.times_layout.times_input_layout.times_combo') - self.use_default_times: QCheckBox = self.get_code('clicker').find_widget('clicker.times_layout.error_use_default_times') - if not self.default_time.text(): - self.use_default_times.setEnabled(False) - self.total_time_label: QLabel = self.get_code('clicker').find_widget('clicker.total_time_label') - case self.page_update: - set_content_label(get_lang('87')) - # 选择更新检查提示 - self.enable_update = UCheckBox(get_lang('48')) # 开启更新 - self.enable_update.setChecked(setting_value.update_enabled) - - update_disable_text = QLabel(get_lang('d0')) # 更新禁止提示 - set_style(update_disable_text, StyleClass.d_11) - - self.update_notify = UCheckBox(get_lang('4a')) # 更新提示 - self.update_notify.setChecked(setting_value.update_notify) - - self.quiet_install = UCheckBox(get_lang('49')) # 静默安装 - self.quiet_install.setChecked(setting_value.quiet_update) - - self.update_ok = UCheckBox(get_lang('4c')) # 更新完成弹出提示 - self.update_ok.setChecked(setting_value.update_ok_notify) - - update_frequency_layout = QHBoxLayout() # 更新频率布局 - self.update_frequency = QComboBox() # 更新频率 - self.update_frequency.addItems([get_lang('bd'), get_lang('be'), get_lang('bf'), get_lang('c0')]) - self.update_frequency.setCurrentIndex(setting_value.update_frequency) - update_frequency_layout.addWidget(QLabel(get_lang('c1'))) - update_frequency_layout.addWidget(self.update_frequency) - update_frequency_layout.addStretch(1) - - # 布局 - layout.addWidget(self.enable_update) - layout.addWidget(update_disable_text) - layout.addWidget(self.update_notify) - layout.addWidget(self.quiet_install) - layout.addWidget(self.update_ok) - layout.addLayout(update_frequency_layout) - - # 连接信号 - self.enable_update.checkStateChanged.connect(self.on_enable_update_changed) - self.update_notify.checkStateChanged.connect(lambda: self.on_setting_changed(self.update_notify.isChecked, SettingText.update_notify)) - self.quiet_install.checkStateChanged.connect(lambda: self.on_setting_changed(self.quiet_install.isChecked, SettingText.quiet_update)) - self.update_ok.checkStateChanged.connect(lambda: self.on_setting_changed(self.update_ok.isChecked, SettingText.update_ok_notify)) - self.update_frequency.currentIndexChanged.connect(lambda: self.on_setting_changed(self.update_frequency.currentIndex, SettingText.update_frequency)) - if dev_flags.get('new_settings', False): - self.update_notify.checkStateChanged.connect(self.on_sync_notice) - self.update_ok.checkStateChanged.connect(self.on_sync_ok_notice) - else: - self.on_enable_update(self.enable_update.isChecked()) - case self.page_hotkey: - set_content_label(get_lang('21')) - - self.hotkey_enabled = UCheckBox(get_lang('c9')) # 热键启用 - self.hotkey_enabled.setChecked(setting_value.hotkey_enabled) - - # 左键连点 - self.left_click_layout = QHBoxLayout() - self.left_click_input = UHotkeyLineEdit() # 左键连点输入框 - self.left_click_input.setText(format_keys(setting_value.left_click_hotkey)) - self.left_repair_button = QPushButton(get_lang('20')) # 还原默认设置按钮 - - # 布局 - self.left_click_layout.addWidget(QLabel(f'{get_lang('0c')}: '), 1) # 左键连点提示 - self.left_click_layout.addWidget(self.left_click_input, 6) - self.left_click_layout.addWidget(self.left_repair_button, 1) - self.left_click_layout.addStretch() - - # 右键连点 - self.right_click_layout = QHBoxLayout() # 右键连点布局 - self.right_click_input = UHotkeyLineEdit() # 右键连点输入框 - self.right_repair_button = QPushButton(get_lang('20')) # 还原默认设置按钮 - - self.right_click_input.setText(format_keys(setting_value.right_click_hotkey)) - - # 布局 - self.right_click_layout.addWidget(QLabel(f'{get_lang('0d')}: '), 1) # 右键连点提示 - self.right_click_layout.addWidget(self.right_click_input, 6) - self.right_click_layout.addWidget(self.right_repair_button, 1) - self.right_click_layout.addStretch() - - # 暂停/重启连点 - self.pause_click_layout = QHBoxLayout() # 暂停/重启连点布局 - self.pause_click_input = UHotkeyLineEdit() # 暂停/重启连点输入框 - self.pause_click_input.setText(format_keys(setting_value.pause_click_hotkey)) - self.pause_repair_button = QPushButton(get_lang('20')) # 还原默认设置按钮 - - # 布局 - self.pause_click_layout.addWidget(QLabel(f'{get_lang('6b')}: '), 1) # 暂停/重启连点提示 - self.pause_click_layout.addWidget(self.pause_click_input, 6) - self.pause_click_layout.addWidget(self.pause_repair_button, 1) - self.pause_click_layout.addStretch() - - # 停止连点 - self.stop_click_layout = QHBoxLayout() # 停止连点布局 - self.stop_click_input = UHotkeyLineEdit() # 停止连点输入框 - self.stop_click_input.setText(format_keys(setting_value.stop_click_hotkey)) - self.stop_repair_button = QPushButton(get_lang('20')) # 还原默认设置按钮 - - # 布局 - self.stop_click_layout.addWidget(QLabel(f'{get_lang('6c')}: '), 1) # 停止连点提示 - self.stop_click_layout.addWidget(self.stop_click_input, 6) - self.stop_click_layout.addWidget(self.stop_repair_button, 1) - self.stop_click_layout.addStretch() - - # 连点属性 - self.click_attr_layout = QHBoxLayout() # 连点属性布局 - self.click_attr_input = UHotkeyLineEdit() # 连点属性输入框 - self.click_attr_input.setText(format_keys(setting_value.click_attr_hotkey)) - self.click_attr_button = QPushButton(get_lang('20')) # 还原默认设置按钮 - - # 布局 - self.click_attr_layout.addWidget(QLabel(f'{get_lang('8c')}: '), 1) # 连点属性提示 - self.click_attr_layout.addWidget(self.click_attr_input, 6) - self.click_attr_layout.addWidget(self.click_attr_button, 1) - self.click_attr_layout.addStretch() - - # 快速连点 - self.fast_click_layout = QHBoxLayout() # 快速连点布局 - self.fast_click_input = UHotkeyLineEdit() # 快速连点输入框 - self.fast_click_input.setText(format_keys(setting_value.fast_click_hotkey)) - self.fast_click_button = QPushButton(get_lang('20')) # 还原默认设置按钮 - - # 布局 - self.fast_click_layout.addWidget(QLabel(f'{get_lang('75')}: '), 1) # 快速连点提示 - self.fast_click_layout.addWidget(self.fast_click_input, 6) - self.fast_click_layout.addWidget(self.fast_click_button, 1) - self.fast_click_layout.addStretch() - - # 主窗口 - self.main_window_layout = QHBoxLayout() # 主窗口布局 - self.main_window_input = UHotkeyLineEdit() # 主窗口输入框 - self.main_window_input.setText(format_keys(setting_value.main_window_hotkey)) - self.main_window_button = QPushButton(get_lang('20')) # 还原默认设置按钮 - - # 布局 - self.main_window_layout.addWidget(QLabel(f'{get_lang('76')}: '), 1) # 主窗口提示 - self.main_window_layout.addWidget(self.main_window_input, 6) - self.main_window_layout.addWidget(self.main_window_button, 1) - self.main_window_layout.addStretch() # 布局 if dev_flags.get('new_settings', False): layout.addWidget(self.hotkey_enabled) - layout.addLayout(self.left_click_layout) - layout.addLayout(self.right_click_layout) - layout.addLayout(self.pause_click_layout) - layout.addLayout(self.stop_click_layout) - layout.addLayout(self.click_attr_layout) - layout.addLayout(self.fast_click_layout) - layout.addLayout(self.main_window_layout) - - # 连接信号 - self.left_click_input.textChanged.connect(lambda: self.on_setting_changed(lambda: parse_hotkey(self.left_click_input), SettingText.left_click_hotkey)) - self.right_click_input.textChanged.connect(lambda: self.on_setting_changed(lambda: parse_hotkey(self.right_click_input), SettingText.right_click_hotkey)) - self.pause_click_input.textChanged.connect(lambda: self.on_setting_changed(lambda: parse_hotkey(self.pause_click_input), SettingText.pause_click_hotkey)) - self.stop_click_input.textChanged.connect(lambda: self.on_setting_changed(lambda: parse_hotkey(self.stop_click_input), SettingText.stop_click_hotkey)) - self.click_attr_input.textChanged.connect(lambda: self.on_setting_changed(lambda: parse_hotkey(self.click_attr_input), SettingText.click_attr_hotkey)) - self.fast_click_input.textChanged.connect(lambda: self.on_setting_changed(lambda: parse_hotkey(self.fast_click_input), SettingText.fast_click_hotkey)) - self.main_window_input.textChanged.connect(lambda: self.on_setting_changed(lambda: parse_hotkey(self.main_window_input), SettingText.main_window_hotkey)) - - self.left_repair_button.clicked.connect(lambda: self.repair_settings(SettingText.left_click_hotkey)) - self.right_repair_button.clicked.connect(lambda: self.repair_settings(SettingText.right_click_hotkey)) - self.pause_repair_button.clicked.connect(lambda: self.repair_settings(SettingText.pause_click_hotkey)) - self.stop_repair_button.clicked.connect(lambda: self.repair_settings(SettingText.stop_click_hotkey)) - self.click_attr_button.clicked.connect(lambda: self.repair_settings(SettingText.click_attr_hotkey)) - self.fast_click_button.clicked.connect(lambda: self.repair_settings(SettingText.fast_click_hotkey)) - self.main_window_button.clicked.connect(lambda: self.repair_settings(SettingText.main_window_hotkey)) - - self.hotkey_enabled.checkStateChanged.connect(self.on_enable_hotkey_changed) - self.on_enable_hotkey_changed(self.hotkey_enabled.isChecked() if dev_flags.get('new_settings', False) else True) + + lang_id = { + "left_click": ["0c", False], + "right_click": ["0d", False], + "pause_click": ["6b", False], + "stop_click": ["6c", False], + "click_attr": ["8c", True], + "main_window": ["76", False] + } + + for hotkey, enabled, lang, k in zip( + setting_value.hotkey_list.values(), + setting_value.hotkey_enabled_list.values(), + lang_id.values(), + setting_keys, + ): + # 设置元件 + input = UHotkeyLineEdit() + input.setText(format_keys(hotkey)) + repair_button = QPushButton(get_lang('20')) # 还原默认设置按钮 + + if not enabled: + repair_button.setEnabled(False) + input.setEnabled(False) + + # 添加布局 + hotkey_layout = QHBoxLayout() + hotkey_layout.addWidget(QLabel(f'{filter_hotkey(get_lang(lang[0])) if lang[1] else get_lang(lang[0])}: '), 1) + hotkey_layout.addWidget(input, 6) + hotkey_layout.addWidget(repair_button, 1) + hotkey_layout.addStretch() + + layout.addLayout(hotkey_layout) + + # 添加列表 + self.hotkeys_widget_list.append([input, repair_button]) + + # 连接信号 + input.textChanged.connect(lambda val, inp=input, key=k: self.on_setting_changed(lambda: parse_hotkey(inp), f'hotkey,hotkeys,{key}')) + repair_button.clicked.connect(lambda b, key=k: self.repair_settings([f'hotkey,hotkeys,{key}', f'hotkey,enabled,{key}'])) case self.page_doc: set_content_label(get_lang('ca')) @@ -3361,7 +2583,7 @@ def parse_hotkey(input: UHotkeyLineEdit): layout.addWidget(desc) layout.addWidget(create_horizontal_line()) - restart = uiml.compile_ui_file(get_resource_path('ui', 'settings', 'bottom.gui')) + restart = uiml.compile_ui_file(get_resource_path('ui', 'settings', 'bottom.gui'), additional=self.addtional_local_value) layout.addLayout(UIWindow(restart).show()) layout.addStretch(1) @@ -3432,23 +2654,9 @@ def on_sync_ok_notice(self, state): def on_enable_hotkey(self, state): '''启用热键''' - # 输入框 - self.left_click_input.setEnabled(state) - self.right_click_input.setEnabled(state) - self.pause_click_input.setEnabled(state) - self.stop_click_input.setEnabled(state) - self.click_attr_input.setEnabled(state) - self.fast_click_input.setEnabled(state) - self.main_window_input.setEnabled(state) - - # 按钮 - self.left_repair_button.setEnabled(state) - self.right_repair_button.setEnabled(state) - self.pause_repair_button.setEnabled(state) - self.stop_repair_button.setEnabled(state) - self.click_attr_button.setEnabled(state) - self.fast_click_button.setEnabled(state) - self.main_window_button.setEnabled(state) + for input, btn in self.hotkeys_widget_list: + input.setEnabled(state) + btn.setEnabled(state) def on_enable_update_changed(self, state): '''更新提示复选框状态改变''' @@ -3456,7 +2664,7 @@ def on_enable_update_changed(self, state): self.on_enable_update(state) if not state: - if MessageBox.warning(self, get_lang('15'), get_lang('c8'), MessageButtonTemplate.YESNO) == 3: + if MessageBox.warning(self, get_lang('15'), get_lang('c8'), MessageButton.YESNO) == MessageButton.ReturnValue.NO: self.enable_update.setCheckState(Qt.Checked) return else: @@ -3485,13 +2693,23 @@ def repair_auto_start(self): auto_start_manager.create_reg() MessageBox.information(self, get_lang('16'), get_lang('d2')) - def repair_settings(self, key: str): - '''还原默认设置''' + def repair_settings(self, key: str | list): + ''' + 还原默认设置 + + :param key: 控制要恢复的设置项,可以是单个键,也可以是多个键组成的列表。 + ''' global settings - if MessageBox.warning(self, get_lang('15'), get_lang('22'), MessageButtonTemplate.YESNO) != 2: # 不确认重置 + if MessageBox.warning(self, get_lang('15'), get_lang('22'), MessageButton.YESNO) != MessageButton.ReturnValue.NO: # 不确认重置 return try: - del settings[key] + if isinstance(key, str): + self.set_nested_value(settings, key, 'del', ) # 删除键:一个 + elif isinstance(key, list): + for k in key: + self.set_nested_value(settings, k, 'del', ) # 删除键:多个 + else: + raise ValueError(f'Invalid key type: {type(key)}') except KeyError: pass save_settings() @@ -3500,7 +2718,7 @@ def repair_settings(self, key: str): def repair_all_settings(self): logger.info('Reset all settings') global settings - if MessageBox.warning(self, get_lang('15'), get_lang('22'), MessageButtonTemplate.YESNO) != 2: # 不确认重置 + if MessageBox.warning(self, get_lang('15'), get_lang('22'), MessageButton.YESNO) != MessageButton.ReturnValue.NO: # 不确认重置 return settings = {} save_settings() @@ -3530,34 +2748,8 @@ def on_need_restart_setting_changed(self, handle, key: str, restart_place: list[ restart_place = list(map(lambda x: get_lang(x, lang_id=lang), restart_place)) - selected_lang_yes = CustonMessageButton(get_lang('01', source=default_button_text, lang_id=lang), QMessageBox.YesRole) - selected_lang_no = CustonMessageButton(get_lang('02', source=default_button_text, lang_id=lang), QMessageBox.AcceptRole) - need_restart = MessageBox.warning(self, get_lang('15', lang_id=lang), f'{get_lang("89", lang_id=lang)}: {", ".join(restart_place)}', [selected_lang_yes, selected_lang_no], selected_lang_yes) - if need_restart == 2: - self.restart() - else: - self.restart_window() - - def on_need_restart_setting_changed_old(self, handle, key: str, restart_place: list[str] = ['a9'], *args): - '''托盘图标选择事件''' - global settings_need_restart - - self.on_setting_changed(handle, key, *args) - settings_need_restart = True - self.restart_button.show() - - lang = self.lang_choice.currentIndex() - if lang >= 1: - lang -= 1 - elif lang == 0: - lang = system_lang - - restart_place = list(map(lambda x: get_lang(x, lang_id=lang), restart_place)) - - selected_lang_yes = CustonMessageButton(get_lang('01', source=default_button_text, lang_id=lang), QMessageBox.YesRole) - selected_lang_no = CustonMessageButton(get_lang('02', source=default_button_text, lang_id=lang), QMessageBox.AcceptRole) - need_restart = MessageBox.warning(self, get_lang('15', lang_id=lang), f'{get_lang("89", lang_id=lang)}: {", ".join(restart_place)}', [selected_lang_yes, selected_lang_no], selected_lang_yes) - if need_restart == 2: + need_restart = MessageBox.warning(self, get_lang('15', lang_id=lang), f'{get_lang("89", lang_id=lang)}: {", ".join(restart_place)}', MessageButton.YESNO, MessageButton.YES) + if need_restart == MessageButton.ReturnValue.YES: self.restart() else: self.restart_window() @@ -3568,16 +2760,39 @@ def restart_window(self): def on_setting_changed(self, handle, key, *args): '''更新检查提示选择事件''' logger.info(f'Setting changed: {key}') - settings[key] = handle(*args) + self.set_nested_value(settings, key, 'set', handle(*args)) save_settings() - def on_default_input_changed(self, default: QLineEdit, key: str, use_default: UCheckBox): - '''默认延迟输入框内容变化事件''' - if not default.text(): - use_default.setEnabled(False) - else: - use_default.setEnabled(True) - self.on_setting_changed(default.text, key) + def set_nested_value(self, dic: dict, path: str, mode:str, value=None) -> None: + ''' + 在字典中按路径设置值。 + - 如果路径不含逗号,则直接设置 dic[path] = value。 + - 如果路径含逗号,则按逗号分割为多级键,逐层递进,在最后一级键处设置值。 + 若中间键不存在,则自动创建新字典;若中间键存在但不是字典,则覆盖为字典(原值丢失)。 + ''' + def check_dic(dic, path, val): + if mode == 'set': + dic[path] = val + elif mode == 'del': + del dic[path] + else: + raise ValueError('Invalid mode: ' + mode) + + if ',' not in path: + check_dic(dic, path, value) + return + + keys = [k.strip() for k in path.split(',')] # 去除可能的空格 + current = dic + # 逐层深入到倒数第二个键 + for key in keys[:-1]: + if key not in current: + check_dic(current, key, {}) + elif not isinstance(current[key], dict): + raise ValueError('Invalid path: ' + path) # 路径中存在无效的键 + current = current[key] + # 在最后一级键处赋值 + check_dic(current, keys[-1], value) def on_page_button_clicked(self, index): '''处理页面按钮点击事件''' @@ -3625,7 +2840,7 @@ def __init__(self): self.init_ui_old() def init_ui(self): - self.main_layout = UIWindow(uiml.compile_ui_file(get_resource_path('ui', 'importExtension.gui'))) + self.main_layout = UIWindow(uiml.compile_ui_file(get_resource_path('ui', 'importExtension.gui'), additional=self.addtional_local_value)) self.setLayout(self.main_layout.show()) logger.debug('Init import extension mode window finished') @@ -3687,7 +2902,7 @@ def __init__(self): # 创建热键监听器 self.hotkey_listener = get_hotkey_listener_instance() - self.hotkey_listener.combination_pressed.connect(self.run_combination) + hotkey_listener.combination_pressed.connect(self.run_combination) # 创建系统托盘图标 self.setup_tray_icon() @@ -3696,7 +2911,6 @@ def __init__(self): clicker.click_changed.connect(main_window.on_click_changed) clicker.stopped.connect(main_window.on_stop) clicker.click_conuter.connect(main_window.on_click_counter) - clicker.started.connect(self.on_start) clicker.started.connect(main_window.on_start) logger.info('Initializing tray app finished') @@ -3738,15 +2952,14 @@ def create_menu(self): pause_action = QAction(get_lang('6b'), self.app) stop_action = QAction(get_lang('6c'), self.app) set_delay_action = QAction(get_lang('75'), self.app) - click_attr_action = QAction(get_lang('8c'), self.app) + click_attr_action = QAction(filter_hotkey(get_lang('8c')), self.app) - set_delay_action.triggered.connect(lambda: self.show_window(fast_click_window)) - left_click_action.triggered.connect(lambda: self.on_combination_pressed(setting_value.left_click_hotkey)) - right_click_action.triggered.connect(lambda: self.on_combination_pressed(setting_value.right_click_hotkey)) - pause_action.triggered.connect(lambda: self.on_combination_pressed(setting_value.pause_click_hotkey)) - stop_action.triggered.connect(lambda: self.on_combination_pressed(setting_value.stop_click_hotkey)) + left_click_action.triggered.connect(lambda: self.run_command(0)) + right_click_action.triggered.connect(lambda: self.run_command(1)) + pause_action.triggered.connect(lambda: self.run_command(2)) + stop_action.triggered.connect(lambda: self.run_command(3)) click_attr_action.triggered.connect(lambda: self.show_window(click_attr_window)) - + tray_menu.addAction(left_click_action) tray_menu.addAction(right_click_action) tray_menu.addAction(pause_action) @@ -3776,7 +2989,6 @@ def on_tray_icon_activated(self, reason): '''处理托盘图标激活事件''' if reason == QSystemTrayIcon.ActivationReason.Trigger: # 左键点击 self.show_window(main_window) - self.refresh() def check_delay(self, input_delay): try: @@ -3798,17 +3010,14 @@ def run(self): '''运行应用程序''' logger.info('Running tray app') code = self.app.exec() + logger.info(f'Main program exited with {code}') if can_update: run_software('updater.old/updater.py', 'updater.old/updater.exe') else: # 进行清理 run_after.run() - logger.info(f'Main program exited with {code}') self.quit() sys.exit(code) - - def refresh(self): - refresh.run() def quit(self, code=lambda: None): if update_window.down_thread is not None: @@ -3869,53 +3078,50 @@ def show_window(self, window: QMainWindow | QDialog): window.hide() else: window.show() - self.refresh() + refresh.run() def on_combination_pressed(self, combination): '''处理组合键事件''' combination = format_keys(combination, source=True) - if all_in_list(combination, setting_value.fast_click_hotkey): - # 处理快速连点组合键 - if clicker.running: - self.tray_icon.showMessage(get_lang('14'), get_lang('af'), QSystemTrayIcon.MessageIcon.Critical, 1000) - else: - self.show_window(fast_click_window) - elif all_in_list(combination, setting_value.main_window_hotkey): - # 处理主窗口组合键 - self.show_window(main_window) - if not main_window.isVisible(): - main_window.is_start_from_tray = True - elif all_in_list(combination, setting_value.click_attr_hotkey): - # 处理连点属性组合键 - self.show_window(click_attr_window) - elif all_in_list(combination, setting_value.left_click_hotkey): - self.on_start_clicker_tray('left') # 左键 - elif all_in_list(combination, setting_value.right_click_hotkey): - self.on_start_clicker_tray('right') # 右键 - elif all_in_list(combination, setting_value.pause_click_hotkey): - if clicker.running: - clicker.pause_click() - if clicker.paused: - self.tray_icon.showMessage(get_lang('6e'), get_lang('71'), QSystemTrayIcon.MessageIcon.Information, 1000) - else: - self.tray_icon.showMessage(get_lang('6e'), get_lang('72'), QSystemTrayIcon.MessageIcon.Information, 1000) - else: - self.tray_icon.showMessage(get_lang('6e'), get_lang('74'), QSystemTrayIcon.MessageIcon.Warning, 1000) - elif all_in_list(combination, setting_value.stop_click_hotkey): - if clicker.running: - main_window.on_stop() - self.tray_icon.showMessage(get_lang('6e'), get_lang('73'), QSystemTrayIcon.MessageIcon.Information, 1000) - else: - self.tray_icon.showMessage(get_lang('6e'), get_lang('74'), QSystemTrayIcon.MessageIcon.Warning, 1000) - - def on_start(self): - '''连点器启动事件''' - if fast_click_window.isVisible(): - fast_click_window.hide() - self.tray_icon.showMessage(get_lang('14'), get_lang('af'), QSystemTrayIcon.MessageIcon.Critical, 1000) + for index, i in enumerate(setting_keys): + if all_in_list(combination, setting_value.hotkey_list[i]): + self.run_command(index) + break + def run_command(self, command): + '''运行命令''' + match command: + case 0: + self.on_start_clicker_tray('left') # 左键 + case 1: + self.on_start_clicker_tray('right') # 右键 + case 2: + if clicker.running: + clicker.pause_click() + if clicker.paused: + self.tray_icon.showMessage(get_lang('6e'), get_lang('71'), QSystemTrayIcon.MessageIcon.Information, 1000) + else: + self.tray_icon.showMessage(get_lang('6e'), get_lang('72'), QSystemTrayIcon.MessageIcon.Information, 1000) + else: + self.tray_icon.showMessage(get_lang('6e'), get_lang('74'), QSystemTrayIcon.MessageIcon.Warning, 1000) + case 3: + if clicker.running: + main_window.on_stop() + self.tray_icon.showMessage(get_lang('6e'), get_lang('73'), QSystemTrayIcon.MessageIcon.Information, 1000) + else: + self.tray_icon.showMessage(get_lang('6e'), get_lang('74'), QSystemTrayIcon.MessageIcon.Warning, 1000) + case 4: + self.show_window(click_attr_window) + case 5: + self.show_window(main_window) + if not main_window.isVisible(): + main_window.is_start_from_tray = True + case _: + raise ValueError(f'Invalid command: {command}') if __name__ == '__main__': + init_success = False + shared_memory = QSharedMemory(mem_id[0]) if shared_memory.attach(): # 已经有一个实例在运行 @@ -3985,11 +3191,13 @@ def on_start(self): DWMNCRP_ENABLED = 1 logger.info('Loading services') + refresh = Refresh() setting_value = SettingValue() clicker = Click() auto_start_manager = StartManager() color_getter = ColorGetter() run_after = RunAfter() + hotkey_listener = get_hotkey_listener_instance() logger.info('Loading value') logger.debug('Loading const value') @@ -4041,10 +3249,19 @@ def on_start(self): # 单位控制 latest_index = 2 + # 热键管理相关 + is_error = True + delay_num = 0 + time_num = 0 + # 其他 - dev_config = parse_dev.parse() # 开发者模式配置 can_run_hotkey = True # 热键是否可用 result = (None, None, None, None) # 更新检查结果 + setting_keys = list(setting_value.hotkey_list.keys()) # 热键列表 + + if in_dev: + logger.info('In development mode') + uiml.set_namespace(is_debug=True) # 系统版本 windows_version = get_windows_version() @@ -4073,14 +3290,12 @@ def on_start(self): logger.info('Loading ui') main_window = MainWindow() - on_input_change(type=InputChange.main_window) # 更新时间估计状态 about_window = AboutWindow() clean_cache_window = CleanCacheWindow() update_ok_window = UpdateOKWindow() update_window = UpdateWindow() click_attr_window = ClickAttrWindow() - fast_click_window = FastSetClickWindow() setting_window = SettingWindow() set_import_extension_window = SetImportExtensionModeWindow() on_input_change(type=InputChange.setting_window) # 更新时间估计状态 @@ -4089,4 +3304,5 @@ def on_start(self): app = TrayApp() app.app.setStyle(setting_value.theme) + init_success = True app.run() \ No newline at end of file diff --git a/Gui/parse_dev.py b/Gui/parse_dev.py deleted file mode 100644 index 83e9551c..00000000 --- a/Gui/parse_dev.py +++ /dev/null @@ -1,31 +0,0 @@ -import json -from sharelibs import get_resource_path - -with open(get_resource_path('dev_data.json'), 'r', encoding='utf-8') as f: - dev_data = json.load(f) # 读取规则 - -def write(bytes:list[int]): - '''写入数据到文件''' - array = bytearray(bytes) - with open('dev.dat', 'wb') as f: - f.write(array) - -def parse(): - '''解析数据''' - config = {} - - try: - with open('dev.dat', 'rb') as f: - data = f.read() - except FileNotFoundError: - data = b'\x00' * len(dev_data) - - # 解析数据 - for index, i in enumerate(dev_data): - value = data[index] - for k, v in i['cases'].items(): - k = int(k) - if value == k: - config[i['config_in_data']] = v - - return config \ No newline at end of file diff --git a/Gui/res/defaultsetting.json b/Gui/res/defaultsetting.json index e31dea76..4ac71c05 100644 --- a/Gui/res/defaultsetting.json +++ b/Gui/res/defaultsetting.json @@ -1 +1,47 @@ -{"select_lang": "!var system_lang", "show_tray_icon": true, "soft_delay": 100, "click_delay": "", "click_times": "", "delay_unit": 0, "times_unit": 0, "failed_use_default": false, "times_failed_use_default": false, "update_enabled": true, "update_notify": true, "quiet_update": false, "update_ok_notify": true, "update_frequency": 1, "select_style": 0, "use_windows_color": true, "theme": "!var default_theme", "left_click_hotkey": ["F2"], "right_click_hotkey": ["F3"], "pause_click_hotkey": ["F4"], "stop_click_hotkey": ["F6"], "click_attr_hotkey": ["Ctrl", "Alt", "A"], "fast_click_hotkey": ["Ctrl", "Alt", "F"], "main_window_hotkey": ["Ctrl", "Alt", "M"], "default_doc_link": "https://xystudiocode.github.io/pyClickMouse/{lang}", "lang_doc": 0, "update_log_path": "updatelog", "hotkey_enabled": false, "show_warning": true, "show_package_warning": true, "feedback": "https://github.com/xystudiocode/pyClickMouse/issues/new/choose", "hide_flags": true} \ No newline at end of file +{ + "select_lang": 0, + "show_tray_icon": true, + "soft_delay": 100, + "click_delay": "", + "click_times": "", + "delay_unit": 0, + "times_unit": 0, + "failed_use_default": false, + "times_failed_use_default": false, + "update_enabled": true, + "update_notify": true, + "quiet_update": false, + "update_ok_notify": true, + "update_frequency": 1, + "select_style": 0, + "use_windows_color": true, + "theme": "!var default_theme", + "hotkey": { + "hotkey_enabled": true, + "hotkeys": { + "left_click": ["F2"], + "right_click": ["F3"], + "pause_click": ["F4"], + "stop_click": ["F6"], + "click_attr": ["Ctrl","Alt","A"], + "main_window": ["Ctrl","Alt","M"] + }, + "enabled": { + "left_click": true, + "right_click": true, + "pause_click": true, + "stop_click": true, + "click_attr": true, + "main_window": true + } + }, + "default_doc_link": "https://xystudiocode.github.io/pyClickMouse/{lang}", + "lang_doc": 0, + "update_log_path": "updatelog", + "show_warning": true, + "show_package_warning": true, + "feedback": "https://github.com/xystudiocode/pyClickMouse/issues/new/choose", + "hide_flags": true, + "modify_using_default_input": false, + "modify_using_default_combo": false +} \ No newline at end of file diff --git a/Gui/res/dev_data.json b/Gui/res/dev_data.json deleted file mode 100644 index 32960f8c..00000000 --- a/Gui/res/dev_data.json +++ /dev/null @@ -1,2 +0,0 @@ -[ -] \ No newline at end of file diff --git a/Gui/res/langs/langs.json b/Gui/res/langs/langs.json index f654dab4..91ce405b 100644 --- a/Gui/res/langs/langs.json +++ b/Gui/res/langs/langs.json @@ -212,7 +212,12 @@ "d3": "Lab", "d4": "To test some experimental features, which may not be stable.", "d5": "Hide \"lab\" when no experimental features", - "d6": "There is already an instance running, if you can't find the window, you can wake it up from the tray." + "d6": "There is already an instance running, if you can't find the window, you can wake it up from the tray.", + "d7": "Modify click attributes using the default values", + "d8": "Modify click units using the default values", + "d9": "If you enable it, click input blanks will use the default\nvalue when another one is filled. (by default, only all of the input boxes is\nblank will use the default value).", + "da": "If you enable it, unit comboboxes as unit, and unit input as number.\nrepresenting the unit (by default, only the combo box equal to the settings will\nuse the default value).", + "db": "All" } }, { @@ -355,7 +360,7 @@ "89": "需要重启以下服务", "8a": "响应延迟越快,软件使用越顺畅,但会造成更多的占用。", "8b": "Clickmouse", - "8c": "连点属性", + "8c": "连点属性(&A)", "8d": "离开菜单栏以显示连点进度", "8e": "扩展(&X)", "90": "官方扩展(&O)", @@ -423,12 +428,17 @@ "ce": "软件启动会检查资源,如果缺少一些资源,将会警告。", "cf": "官方扩展包丢失警告", "d0": "我们不建议关闭更新,因为这会让你的clickmouse变得不稳定。", - "d1": "如果你的开机自启动出现问题,或打开了clickmouse窗口,可尝试点击\n它来修复。", + "d1": "如果你的开机自启动出现问题,或打开了clickmouse窗口,可尝试点\n击它来修复。", "d2": "修复成功。", "d3": "实验室", "d4": "用于测试一些功能,可能不稳定。", "d5": "无实验项时候隐藏\"实验室\"设置项", - "d6": "已经有一个实例在运行,如果你没有找到窗口,可前往托盘唤醒。" + "d6": "已经有一个实例在运行,如果你没有找到窗口,可前往托盘唤醒。", + "d7": "使用默认值修改连点属性", + "d8": "使用默认值修改连点单位", + "d9": "如果启用,点击输入框空白时会使用默认值,当其他输入框有值时,不会使用默认值。\n(默认情况下,只有所有输入框都为空时才会使用默认值)", + "da": "如果启用,单位组合框作为单位,单位输入框作为数字代表单位(默认情况下,只有设置\n的值等于组合框的值时才会使用默认值)", + "db": "全部" } } ] \ No newline at end of file diff --git a/Gui/res/styles/dark.qss b/Gui/res/styles/dark.qss index 962e958b..d18b5463 100644 --- a/Gui/res/styles/dark.qss +++ b/Gui/res/styles/dark.qss @@ -227,5 +227,5 @@ QStatusBar { /* 元数据 */ .meta { - --mode: dark; + mode: dark; } \ No newline at end of file diff --git a/Gui/res/styles/light.qss b/Gui/res/styles/light.qss index 56468374..0c9441fe 100644 --- a/Gui/res/styles/light.qss +++ b/Gui/res/styles/light.qss @@ -219,5 +219,5 @@ QStatusBar { /* 元数据 */ .meta { - --mode: light; + mode: light; } \ No newline at end of file diff --git a/Gui/res/ui/clickattr.gui b/Gui/res/ui/clickattr.gui index 17f2fc61..7cb40d19 100644 --- a/Gui/res/ui/clickattr.gui +++ b/Gui/res/ui/clickattr.gui @@ -1,12 +1,12 @@ - - + + - + \ No newline at end of file diff --git a/Gui/res/ui/fastClick.gui b/Gui/res/ui/fastClick.gui deleted file mode 100644 index 0dd1785f..00000000 --- a/Gui/res/ui/fastClick.gui +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Gui/res/ui/settings/clicker.gui b/Gui/res/ui/settings/clicker.gui index 7ea422d1..dec300c9 100644 --- a/Gui/res/ui/settings/clicker.gui +++ b/Gui/res/ui/settings/clicker.gui @@ -1,6 +1,23 @@ + + + + 、 + + + @@ -25,7 +42,7 @@ name='error_use_default_delay' args=['!lang 47'] init_steps=[{'name': 'setChecked', 'args': [setting_value.delay_error_use_default]}] - signals={'checkStateChanged': lambda: self.on_setting_changed(self.use_default_delay.isChecked, SettingText.delay_error_use_default)} + signals={'checkStateChanged': lambda: self.on_clicker_changed(self.use_default_delay.isChecked, SettingText.delay_error_use_default)} /> @@ -50,11 +67,11 @@ - + \ No newline at end of file diff --git a/Gui/sharelibs.py b/Gui/sharelibs.py index efb55db1..20764d1d 100644 --- a/Gui/sharelibs.py +++ b/Gui/sharelibs.py @@ -10,7 +10,6 @@ import os import subprocess import winreg -import sys import ctypes import win32com.client import hashlib @@ -19,14 +18,6 @@ setting_path = Path('data', 'settings.json') setting_path.parent.mkdir(parents=True, exist_ok=True) - -def _show_message(message, title, status): - if status == 0: - QMessageBox.information(None, title, message) - elif status == 1: - QMessageBox.warning(None, title, message) - elif status == 2: - QMessageBox.critical(None, title, message) def multi_replace(text, replace_dict): '''一次性替换多个子串''' @@ -40,31 +31,20 @@ def get_resource_path(*paths): ''' 获取资源文件路径 ''' - try: - resource = Path('res') # 获取当前目录的资源文件夹路径 - if not resource.exists(): - raise FileNotFoundError('Resource folder missing: res not found') - return str(resource.joinpath(*paths)) - except Exception as e: - _show_message(f'Resource file missing: {e}', 'Error', 2) - sys.exit(1) - -try: - lang_path = Path('res', 'langs') - with open(lang_path / 'langs.json', 'r', encoding='utf-8') as f: - langs = json.load(f) - - with open(lang_path / 'control.json', 'r', encoding='utf-8') as f: - control_langs = json.load(f) + resource = Path('res') # 获取当前目录的资源文件夹路径 + if not resource.exists(): + raise FileNotFoundError('Resource not found') + return str(resource.joinpath(*paths)) + +lang_path = Path('res', 'langs') +with open(lang_path / 'langs.json', 'r', encoding='utf-8') as f: + langs = json.load(f) - with open(lang_path / 'init.json', 'r', encoding='utf-8') as f: - init_langs = json.load(f) -except FileNotFoundError: - _show_message('Resource file missing: langs not found', 'Error', 2) - sys.exit(1) -except json.JSONDecodeError: - _show_message('Resource file damaged: langs format error', 'Error', 2) - sys.exit(1) +with open(lang_path / 'control.json', 'r', encoding='utf-8') as f: + control_langs = json.load(f) + +with open(lang_path / 'init.json', 'r', encoding='utf-8') as f: + init_langs = json.load(f) def load_settings(): ''' @@ -313,7 +293,8 @@ def widget_replacer(ui_data: str): else: return uiml.default_replacer(ui_data) # 交由uiml进行替换 -def layout_parser(ui_data: Dict[str, Any], namespace=None): +def layout_parser(ui_data: Dict[str, Any], namespace=None, additional=None): + additional_value = additional if additional is not None else {} # 额外参数 if not ui_data.get('show_if', True): return None if ui_data.get('direction').lower() == 'u': @@ -323,22 +304,27 @@ def layout_parser(ui_data: Dict[str, Any], namespace=None): # 索引2 -- 选择框 inputs = ui_data['content'][1] for input in inputs['content']: - input_compiled_list.append(uiml.compile_ui(input, namespace)) # 递归解析 + input_compiled_list.append(uiml.compile_ui(input, namespace, additional_value)) # 递归解析 combos = ui_data['content'][2] # 组合框 combos_compiled_list = [] for combo in combos['content']: - combos_compiled_list.append(uiml.compile_ui(combo, namespace)) # 递归解析 + combos_compiled_list.append(uiml.compile_ui(combo, namespace, additional_value)) # 递归解析 return {'name': ui_data.get('name'), 'direction': ui_data.get('direction'), "texts": ui_data['content'][0]['values'], 'inputs': input_compiled_list, 'combos': combos_compiled_list} - return uiml.default_layout_parser(ui_data, namespace) # 交由uiml进行替换 + return uiml.default_layout_parser(ui_data, namespace, additional_value) # 交由uiml进行替换 -def widget_parser(ui_data: Dict[str, Any], namespace=None): +def widget_parser(ui_data: Dict[str, Any], namespace=None, additional=None): + additional_value = additional if additional is not None else {} # 额外参数 show_value = ui_data.get('show_if', True) - uiml_value = uiml.default_widget_parser(ui_data, namespace) # 交由uiml进行替换 + uiml_value = uiml.default_widget_parser(ui_data, namespace, additional_value) # 交由uiml进行替换 if not show_value: return None return uiml_value -uiml.set_namespace(value_replace_func=widget_replacer, layout_parser_func=layout_parser, widget_parser_func=widget_parser, additional_used_widget_key=['show_if'], additional_used_layout_key=['show_if'], reverse=True) # 设置uiml的控制函数 +uiml.set_namespace( + value_replace_func=widget_replacer, layout_parser_func=layout_parser, widget_parser_func=widget_parser, + additional_used_widget_key=['show_if'], additional_used_layout_key=['show_if'], reverse=True, + enable_value_convert=True +) # 设置uiml的控制函数 class UIWindow(uiml.UIMLLayout): def __init__(self, list=None): diff --git a/Gui/tests/test_nest.py b/Gui/tests/test_nest.py new file mode 100644 index 00000000..923df50d --- /dev/null +++ b/Gui/tests/test_nest.py @@ -0,0 +1,45 @@ +def set_nested_value(dic: dict, path: str, mode:str, value) -> None: + ''' + 在字典中按路径设置值。 + - 如果路径不含逗号,则直接设置 dic[path] = value。 + - 如果路径含逗号,则按逗号分割为多级键,逐层递进,在最后一级键处设置值。 + 若中间键不存在,则自动创建新字典;若中间键存在但不是字典,则覆盖为字典(原值丢失)。 + ''' + def check_dic(dic, path, val): + if mode == 'set': + dic[path] = val + elif mode == 'del': + del dic[path] + else: + raise ValueError('Invalid mode: ' + mode) + + if ',' not in path: + check_dic(dic, path, value) + return + + keys = [k.strip() for k in path.split(',')] # 去除可能的空格 + current = dic + # 逐层深入到倒数第二个键 + for key in keys[:-1]: + if key not in current: + check_dic(current, key, {}) + elif not isinstance(current[key], dict): + raise ValueError('Invalid path: ' + path) # 路径中存在无效的键 + current = current[key] + # 在最后一级键处赋值 + check_dic(current, keys[-1], value) + +def test_main(): + dic = {'a': {'b': {'c': 1}}} + set_nested_value(dic, 'a,b,c', 'set', 2) + print(dic) # 输出 {'a': {'b': {'c': 2}}} + assert dic['a']['b']['c'] == 2 + set_nested_value(dic, 'a,b,c', 'del', None) + print(dic) # 输出 {'a': {'b': {}}} + assert 'c' not in dic['a']['b'] + set_nested_value(dic, 'a,d', 'set', 3) + assert dic['a']['d'] == 3 + print(dic) # 输出 {'a': {'b': {}, 'd': 3}} + +if __name__ == '__main__': + test_main() \ No newline at end of file diff --git a/Gui/txtinfo.py b/Gui/txtinfo.py index 19825be0..07ecbda9 100644 --- a/Gui/txtinfo.py +++ b/Gui/txtinfo.py @@ -20,13 +20,7 @@ class SettingText: select_style = 'select_style' use_windows_color = 'use_windows_color' theme = 'theme' - left_click_hotkey = 'left_click_hotkey' - right_click_hotkey = 'right_click_hotkey' - pause_click_hotkey = 'pause_click_hotkey' - stop_click_hotkey ='stop_click_hotkey' - click_attr_hotkey = 'click_attr_hotkey' - fast_click_hotkey = 'fast_click_hotkey' - main_window_hotkey = 'main_window_hotkey' + hotkey = 'hotkey' default_doc_link = 'default_doc_link' lang_doc = 'lang_doc' update_log_path = 'update_log_path' @@ -35,14 +29,13 @@ class SettingText: show_package_warning ='show_package_warning' feedback = 'feedback' hide_flags = 'hide_flags' + modify_using_default_input = 'modify_using_default_input' + modify_using_default_combo = 'modify_using_default_combo' class SettingValue: def get(self, value): default_value = default_settings.get(value, None) - if isinstance(default_value, str): - if default_value.startswith('!var '): # 需要加载变量 - var_name = default_value[5:] - default_value = eval(var_name) + return settings.get(value, default_value) def __getitem__(self, key): @@ -122,33 +115,19 @@ def use_windows_color(self): def theme(self): return self[SettingText.theme] - @property - def left_click_hotkey(self): - return self[SettingText.left_click_hotkey] - - @property - def right_click_hotkey(self): - return self[SettingText.right_click_hotkey] - - @property - def pause_click_hotkey(self): - return self[SettingText.pause_click_hotkey] - - @property - def stop_click_hotkey(self): - return self[SettingText.stop_click_hotkey] - - @property - def click_attr_hotkey(self): - return self[SettingText.click_attr_hotkey] + def _get_hotkey(self, val_name): + setting_value = settings.get(SettingText.hotkey, {}).get(val_name, {}) + default_value = default_settings.get(SettingText.hotkey, {}).get(val_name, {}) + + return default_value | setting_value # 右侧覆盖左侧 @property - def fast_click_hotkey(self): - return self[SettingText.fast_click_hotkey] + def hotkey_list(self): + return self._get_hotkey('hotkeys') @property - def main_window_hotkey(self): - return self[SettingText.main_window_hotkey] + def hotkey_enabled_list(self): + return self._get_hotkey('enabled') @property def default_doc_link(self): @@ -164,7 +143,7 @@ def update_log_path(self): @property def hotkey_enabled(self): - return self[SettingText.hotkey_enabled] + return self[SettingText.hotkey].get(SettingText.hotkey_enabled, True) @property def show_warning(self): @@ -182,6 +161,14 @@ def feedback(self): def hide_flags(self): return self[SettingText.hide_flags] + @property + def modify_using_default_input(self): + return self[SettingText.modify_using_default_input] + + @property + def modify_using_default_combo(self): + return self[SettingText.modify_using_default_combo] + class StyleClass: big_16 = 'big_text_16' big_20 = 'big_text_20' diff --git a/Gui/uiStyles/widgets.py b/Gui/uiStyles/widgets.py index 73abd9b8..8d8b91e4 100644 --- a/Gui/uiStyles/widgets.py +++ b/Gui/uiStyles/widgets.py @@ -1,9 +1,18 @@ -from uiStyles.QUI import * -from sharelibs import get_lang,default_button_text +# from uiStyles.QUI import * +from PySide6.QtWidgets import * +from PySide6.QtCore import * +from PySide6.QtGui import * +import ctypes # 用于获取系统风格 +from sharelibs import get_lang, default_button_text -__all__ = ['UMessageBox', 'VScrollArea', 'HScrollArea', 'UCheckBox', 'UnitInputLayout', 'ULabel', 'MessageButtonTemplate', 'CustonMessageButton'] +__all__ = ['UMessageBox', 'VScrollArea', 'HScrollArea', 'UCheckBox', 'UnitInputLayout', 'ULabel', 'MessageButton', 'CustonMessageButton', 'MessageIcon'] -class MessageButtonTemplate: +# 定义消息框图标对应的声音常量 +MB_ICONHAND = 0x00000010 # 错误(手形) +MB_ICONEXCLAMATION = 0x00000030 # 警告 +MB_ICONASTERISK = 0x00000040 # 信息 + +class MessageButton: NOBUTTON = 0b0 YES = 0b1 NO = 0b10 @@ -12,85 +21,167 @@ class MessageButtonTemplate: YESNO = YES | NO OKCANCEL = OK | CANCEL + class ReturnValue: + YES = 0 + NO = 1 + OK = 2 + CANCEL = 3 + +class MessageIcon: + INFO = 0b1 + WARNING = 0b10 + CRITICAL = 0b100 + QUESTION = 0b1000 + class CustonMessageButton: def __init__(self, text, role): self.text = text self.role = role -class UMessageBox(QMessageBox): - @staticmethod - def new_msg(parent, - title: str, - text: str, - icon: QMessageBox.Icon, - buttons: MessageButtonTemplate = MessageButtonTemplate.OK, - defaultButton: MessageButtonTemplate = MessageButtonTemplate.OK): - - msg_box = QMessageBox(icon, title, text, buttons=QMessageBox.NoButton, parent=parent) - - default_btn = None - - # 虽然下面的规则匹配有点奇怪,但是为了显示整齐所以要这样写 +class UMessageBox(QDialog): + def __init__(self, parent: QWidget | None, title: str, text: str, icon: MessageIcon, buttons: MessageButton, defaultButton: MessageButton): + super().__init__(parent) + + self.setWindowTitle(title) + + self.text = text + match icon: + case MessageIcon.INFO: + self.icon = QStyle.SP_MessageBoxInformation + case MessageIcon.WARNING: + self.icon = QStyle.SP_MessageBoxWarning + case MessageIcon.CRITICAL: + self.icon = QStyle.SP_MessageBoxCritical + case MessageIcon.QUESTION: + self.icon = QStyle.SP_MessageBoxQuestion + case _: + raise ValueError('Invalid icon value') + + self.buttons = {} + self.defaultButton = None + + self.btn_id = 5 # 前四个为默认按钮id,后面为自定义按钮id + if isinstance(buttons, int): - if buttons & MessageButtonTemplate.YES: - btn = msg_box.addButton(get_lang('01', source=default_button_text), QMessageBox.YesRole) - if defaultButton == MessageButtonTemplate.YES: - default_btn = btn - - if buttons & MessageButtonTemplate.NO: - btn = msg_box.addButton(get_lang('02', source=default_button_text), QMessageBox.AcceptRole) - if defaultButton == MessageButtonTemplate.NO: - default_btn = btn - - if buttons & MessageButtonTemplate.OK: - btn = msg_box.addButton(get_lang('03', source=default_button_text), QMessageBox.NoRole) - if defaultButton == MessageButtonTemplate.OK: - default_btn = btn - - if buttons & MessageButtonTemplate.CANCEL: - btn = msg_box.addButton(get_lang('04', source=default_button_text), QMessageBox.RejectRole) - if defaultButton == MessageButtonTemplate.CANCEL: - default_btn = btn - elif isinstance(buttons, CustonMessageButton): - btn = msg_box.addButton(buttons.text, buttons.role) - if defaultButton == buttons: - default_btn = btn - elif isinstance(buttons, list): + if buttons & MessageButton.YES: + btn = QPushButton(get_lang('01', source=default_button_text)) + msg_id = 1 + + self.buttons[msg_id] = btn + if defaultButton == MessageButton.YES: + self.defaultButton = btn + if buttons & MessageButton.NO: + btn = QPushButton(get_lang('02', source=default_button_text)) + msg_id = 2 + self.buttons[msg_id] = btn + + if defaultButton == MessageButton.NO: + self.defaultButton = btn + if buttons & MessageButton.OK: + btn = QPushButton(get_lang('03', source=default_button_text)) + msg_id = 3 + self.buttons[msg_id] = btn + + if defaultButton == MessageButton.OK: + self.defaultButton = btn + if buttons & MessageButton.CANCEL: + btn = QPushButton(get_lang('04', source=default_button_text)) + msg_id = 4 + self.buttons[msg_id] = btn + + if defaultButton == MessageButton.CANCEL: + self.defaultButton = btn + if isinstance(buttons, CustonMessageButton): + btn = QPushButton(buttons.text) + msg_id = self.btn_id + self.btn_id += 1 + self.buttons[msg_id] = btn + + if defaultButton == btn: + self.defaultButton = btn + if isinstance(buttons, list): for button in buttons: if isinstance(button, CustonMessageButton): - btn = msg_box.addButton(button.text, button.role) + btn = QPushButton(button.text, button.role) + msg_id = self.btn_id + self.buttons[msg_id] = btn if defaultButton == button: - default_btn = btn + self.defaultButton = btn else: - raise ValueError('buttons must be a list of CustonMessageButton') # 报错 - else: - raise ValueError('buttons must be a int or a list of CustonMessageButton') # 报错 - - if default_btn: - msg_box.setDefaultButton(default_btn) + raise ValueError('buttons must be a list of CustonMessageButton') - return msg_box - - @classmethod - def warning(cls, parent, title: str, text: str, buttons: MessageButtonTemplate = MessageButtonTemplate.OK, defaultButton: MessageButtonTemplate = MessageButtonTemplate.OK): - msg_box = cls.new_msg(parent, title, text, QMessageBox.Icon.Warning, buttons, defaultButton) - return msg_box.exec() + self.init_ui() + + def init_ui(self): + layout = QVBoxLayout(self) + + content_layout = QHBoxLayout() + icon = QLabel() + show_icon = icon.style().standardIcon(self.icon) + icon.setPixmap(show_icon.pixmap(32, 32)) + + # 将 label 保存为实例属性,以便后续设置最大宽度 + label = QLabel(self.text) + label.setAlignment(Qt.AlignLeft | Qt.AlignTop) + label.setWordWrap(True) # 文本换行 + label.setMaximumWidth(500) + + content_layout.addWidget(icon, 0) + content_layout.addWidget(label, 1) + buttons_layout = QHBoxLayout() + buttons_layout.addStretch() + buttons_layout.setContentsMargins(0, 8, 0, 0) + for k, button in self.buttons.items(): + button.clicked.connect(lambda b, key=k: self.done(key)) # 为按钮添加点击事件 + buttons_layout.addWidget(button) + if button == self.defaultButton: + button.setDefault(True) + + layout.addLayout(content_layout) + layout.addLayout(buttons_layout) + + self.setLayout(layout) + + def showEvent(self, event): + match self.icon: + case QStyle.SP_MessageBoxInformation: + sound = MB_ICONASTERISK + case QStyle.SP_MessageBoxWarning: + sound = MB_ICONEXCLAMATION + case QStyle.SP_MessageBoxCritical: + sound = MB_ICONHAND + case _: + sound = None + + if sound is not None: + ctypes.windll.user32.MessageBeep(sound) + return super().showEvent(event) + @classmethod - def critical(cls, parent, title: str, text: str, buttons: MessageButtonTemplate = MessageButtonTemplate.OK, defaultButton: MessageButtonTemplate = MessageButtonTemplate.OK): - msg_box = cls.new_msg(parent, title, text, QMessageBox.Icon.Critical, buttons, defaultButton) - return msg_box.exec() + def infomation(cls, parent: QWidget | None, title: str, text: str, buttons: MessageButton = None, defaultButton: MessageButton = None): + buttons = MessageButton.OK if buttons is None else buttons + defaultButton = MessageButton.OK if buttons is None else defaultButton + return cls(parent, title, text, MessageIcon.INFO, buttons, defaultButton).exec() @classmethod - def information(cls, parent, title: str, text: str, buttons: MessageButtonTemplate = MessageButtonTemplate.OK, defaultButton: MessageButtonTemplate = MessageButtonTemplate.OK): - msg_box = cls.new_msg(parent, title, text, QMessageBox.Icon.Information, buttons, defaultButton) - return msg_box.exec() + def warning(cls, parent: QWidget | None, title: str, text: str, buttons: MessageButton = None, defaultButton: MessageButton = None): + buttons = MessageButton.OK if buttons is None else buttons + defaultButton = MessageButton.OK if buttons is None else defaultButton + return cls(parent, title, text, MessageIcon.WARNING, buttons, defaultButton).exec() @classmethod - def question(cls, parent, title: str, text: str, buttons: MessageButtonTemplate = MessageButtonTemplate.YESNO, defaultButton: MessageButtonTemplate = MessageButtonTemplate.YES): - msg_box = cls.new_msg(parent, title, text, QMessageBox.Icon.Question, buttons, defaultButton) - return msg_box.exec() - + def critical(cls, parent: QWidget | None, title: str, text: str, buttons: MessageButton = None, defaultButton: MessageButton = None): + buttons = MessageButton.OK if buttons is None else buttons + defaultButton = MessageButton.OK if buttons is None else defaultButton + return cls(parent, title, text, MessageIcon.CRITICAL, buttons, defaultButton).exec() + + @classmethod + def question(cls, parent: QWidget | None, title: str, text: str, buttons: MessageButton = None, defaultButton: MessageButton = None): + buttons = MessageButton.YESNO if buttons is None else buttons + defaultButton = MessageButton.YESNO if buttons is None else defaultButton + return cls(parent, title, text, MessageIcon.QUESTION, buttons, defaultButton).exec() + class VScrollArea(QScrollArea): def __init__(self, parent=None): super().__init__(parent) diff --git a/clickmouse_api/clickmouse_API/command.py b/MANIFEST.in similarity index 100% rename from clickmouse_api/clickmouse_API/command.py rename to MANIFEST.in diff --git a/README-py.md b/README-py.md new file mode 100644 index 00000000..89b00b03 --- /dev/null +++ b/README-py.md @@ -0,0 +1,18 @@ +# Python package usage + +## 📝 Installation +```bash +pip install ClickMouse +``` + +## 🚀 Usage +For Python call or .pyd call, use the following code: +```python +import clickmouse + +clickmouse.click_mouse(clickmouse.LEFT, 1000, 10, 10) # click left button 10 times, interval 1000ms, press duration 10ms +``` +~~Command line call~~ +```bash +ClickMouse.exe /h # show help +``` diff --git a/clickmouse_api/README.md b/clickmouse_api/README.md deleted file mode 100644 index ba8adb02..00000000 --- a/clickmouse_api/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# ClickMouse API工具库 - -使用clickmouse api来实现clickmouse扩展开发! -导入方法: -```python -from clickmouse_APIv1 as clickmouse -``` -语言包格式: -```json -[ - { - "lang_id": 0, - "lang_system_name":"en", - "lang_package": { - "01": "This is the language package file", - } - }, - { - "lang_id": 1, - "lang_system_name": "zh-CN", - "lang_package": { - "01": "这是语言包文件", - } - } -] -``` -编辑扩展完成后,打包为exe,使用clickmouse导入测试 -若clickmouse已经安装,设置的数据将会导入到clickmouse中 \ No newline at end of file diff --git a/clickmouse_api/clickmouse_API/GUI/__init__.py b/clickmouse_api/clickmouse_API/GUI/__init__.py deleted file mode 100644 index 52e36a6f..00000000 --- a/clickmouse_api/clickmouse_API/GUI/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from wx import * -from clickmouse_API.GUI import styles diff --git a/clickmouse_api/pyproject.toml b/clickmouse_api/pyproject.toml deleted file mode 100644 index 3b5ba98d..00000000 --- a/clickmouse_api/pyproject.toml +++ /dev/null @@ -1,32 +0,0 @@ -[build-system] -requires = ["setuptools>=61.0.0"] -build-backend = "setuptools.build_meta" - -[project] -name = "ClickMouse_api" -version = "1.0.1" -authors = [ - {name = "xystudio", email = "173288240@qq.com"} -] -description = "基于Python的鼠标连点工具扩展api" -readme = "README.md" -requires-python = ">=3.8" -license = {text = "MIT"} -keywords = ["mouse", "click", "automation", "clickmouse", "api"] -classifiers = [ - "Programming Language :: Python :: 3", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", -] -dependencies = [ - "wxpython" -] - -[project.urls] -Homepage = "https://github.com/xystudiocode/pyClickMouse" - -[project.scripts] -clickmouse = "clickmouse.__main__:main" - -[project.optional-dependencies] -clickmouse_control = ["clickmouse"] \ No newline at end of file diff --git a/clickmouse_api/setup.py b/clickmouse_api/setup.py deleted file mode 100644 index 887cfd28..00000000 --- a/clickmouse_api/setup.py +++ /dev/null @@ -1,37 +0,0 @@ -"""这个是使用setuptools打包的脚本,用于安装使用python调用的clickmouse工具 -这个库仅供开发人员使用,普通人请前往github releases(https://github.com/xystudio/pyClickMouse/releases) -推荐通过'pip install clickmouse'安装,若网络较差,可以使用清华镜像源'pip install clickmouse -i https://pypi.tuna.tsinghua.edu.cn/simple' -若要下载本地安装包,建议下载whl格式的安装包,下载地址:https://github.com/xystudio/pyClickMouse/releases,下载结束后在下载位置打开命令行,运行'python setup.py install'""" - -# 以下为setup.py文件内容 -# 导入setuptools模块 -import setuptools - -# 定义setup函数 -setuptools.setup( - name="ClickMouse_api", # 包名 - version="1.0.1", # 版本号 - author="xystudio", # 作者 - author_email="173288240@qq.com", # 作者邮箱 - description="基于Python的鼠标连点工具扩展api", # 包描述 - url="https://github.com/xystudiocode/pyClickMouse", # 包的github地址 - long_description=open("README.md", "r", encoding="utf-8").read(), # 包的readme文件 - long_description_content_type="text/markdown", # 指定readme文件格式为markdown - packages=setuptools.find_packages(), # 包的目录结构 - classifiers=[ # 包的分类列表 - "Programming Language :: Python :: 3", # python版本 - "License :: OSI Approved :: MIT License", # 许可证 - "Operating System :: OS Independent",# 系统 - ], - keywords=["mouse", "click", "automation", "clickmouse", "api"], # 包的关键字列表 - python_requires='>=3.8', # python版本要求 - install_requires=['wxpython'], # 依赖的包列表 - entry_points={ # 脚本入口 - "console_scripts": [ - "clickmouse = clickmouse.__main__:main", - ] - }, - extras_require = { - "clickmouse_control": ["clickmouse"] - } -) diff --git a/cython/main.py b/cython/main.py deleted file mode 100644 index 18f9d1ba..00000000 --- a/cython/main.py +++ /dev/null @@ -1,16 +0,0 @@ -""" -ClickMouse - pyd库 ---- -该库提供了鼠标点击操作的函数。 - -使用方法: -import clickmouse - -# 点击鼠标左键 -clickmouse.click_mouse(clickmouse.LEFT, 100, 100, 10) # 鼠标左键点击10次,延迟为100毫秒,按下时间为100毫秒 -clickmouse.click_mouse(clickmouse.RIGHT, 100, 100, 10) # 鼠标右键点击10次,延迟为100毫秒,按下时间为100毫秒 -""" - -# 需要的库和功能 -from clickmouse import * -from clickmouse import __author__, __version__ \ No newline at end of file diff --git a/cython/setup.py b/cython/setup.py deleted file mode 100644 index 003136a9..00000000 --- a/cython/setup.py +++ /dev/null @@ -1,12 +0,0 @@ -"""这个是使用setuptools打包的脚本,用于安装使用pyd调用的clickmouse工具 -这个库仅供开发人员使用,普通人请前往github releases(https://github.com/xystudio/pyClickMouse/releases)下载安装包 -""" -# 导入模块 -from setuptools import setup -from Cython.Build import cythonize - -# 定义setup函数 -setup( - ext_modules=cythonize(["main.py"]), # 编译main.py为.pyd文件 - install_requires=["clickmouse"], # 依赖模块 -) \ No newline at end of file diff --git a/documents/.vitepress/config.js b/documents/.vitepress/config.js index 7e31e758..ccd92ef9 100644 --- a/documents/.vitepress/config.js +++ b/documents/.vitepress/config.js @@ -233,6 +233,10 @@ export default withMermaid({ text: 'v3.x.x.x', collapsed: true, items: [ + { + text: '3.2.3.22', + link: '/en/updatelog/beta/3/33023a6', + }, { text: '3.3.0.23alpha5', link: '/en/updatelog/beta/3/33023a5', @@ -649,6 +653,10 @@ export default withMermaid({ text: 'v3.x.x.x', collapsed: true, items: [ + { + text: '3.2.3.22', + link: '/zh-CN/updatelog/beta/3/33023a6', + }, { text: '3.3.0.23alpha5', link: '/zh-CN/updatelog/beta/3/33023a5', diff --git a/documents/en/features/settings.md b/documents/en/features/settings.md index 02b6cce5..0927fe61 100644 --- a/documents/en/features/settings.md +++ b/documents/en/features/settings.md @@ -314,6 +314,7 @@ Setting items include: - - Type: Checkbox - - Default value: Off - - Field name: `delay_error_use_default` + This operation is disabled when Click Delay parameter is set to empty @@ -322,6 +323,14 @@ This operation is disabled when Click Delay parameter is set to emp If turn off this option, only when click is empty will use default value; after enabling, as long as input format error will use default value. +- Modify click attributes using the default values: If enabled, then in the click delay change, the default value will continue to use. +- - Type:Checkbox +- - Field name: Off +- - Field name: `modify_using_default_input` +- Modify click units using the default values: If enabled, then in the unit change, the default value will continue to use. +- - Type:Checkbox +- - Field name: Off +- - Field name: `modify_using_default_combo` - Click Count Default Value: Control default count when click count is empty - - Type: Input box - - Default value: Empty @@ -405,35 +414,31 @@ Setting items include: - - Type: Checkbox - - Default value: On -- - Field name: `hotkey_enabled` +- - Field name: `hotkey,hotkey_enabled` - Left Click Hotkey: Set left click hotkey. - - Type: Input box - - Default value: `F2` -- - Field name: `left_click_hotkey` +- - Field name: `hotkey,hotkeys,left_click_hotkey` - Right Click Hotkey: Set right click hotkey. - - Type: Input box - - Default value: `F3` -- - Field name: `right_click_hotkey` +- - Field name: `hotkey,hotkeys,right_click_hotkey` - Pause/Restart Click Hotkey: Set pause/restart clicker hotkey. - - Type: Input box - - Default value: `F4` -- - Field name: `pause_click_hotkey` +- - Field name: `hotkey,hotkeys,pause_click_hotkey` - Stop Click Hotkey: Set stop click hotkey. - - Type: Input box - - Default value: `F6` -- - Field name: `stop_click_hotkey` +- - Field name: `hotkey,hotkeys,stop_click_hotkey` - Click Attribute Hotkey: Set open click attribute hotkey. - - Type: Input box - - Default value: `Ctrl+Alt+A` -- - Field name: `click_attr_hotkey` -- Fast Click Hotkey: Set open fast click hotkey. -- - Type: Input box -- - Default value: `Ctrl+Alt+F` -- - Field name: `fast_click_hotkey` +- - Field name: `hotkey,hotkeys,click_attr_hotkey` - Main Window Hotkey: Set open main window hotkey. - - Type: Input box - - Default value: `Ctrl+Alt+M` -- - Field name: `main_window_hotkey` +- - Field name: `hotkey,hotkeys,main_window_hotkey` - Reset Left Click Settings: Used to restore default left click settings. - - Type: Button - Reset Right Click Settings: Used to restore default right click settings. diff --git a/documents/en/updatelog/beta/3/33023a6.md b/documents/en/updatelog/beta/3/33023a6.md new file mode 100644 index 00000000..e5f61c46 --- /dev/null +++ b/documents/en/updatelog/beta/3/33023a6.md @@ -0,0 +1,32 @@ +--- +title: 3.0.0.11alpha2 +layout: doc +--- + +# 3.0.0.11alpha2 + +Release Date: 2026/06/27 + +> This is a development test version, the 5th release in the `3.3.0` development cycle. + +## What's New +- [refactor] Upgraded the logging system, more detailed +- [refactot] Replaced some UI elements, enforcing the new UI +- [feat] Added two new settings for the clicker: +- - Changing some input box content won’t affect calculations +- - Changing some units won’t affect calculations +- [fix] Fixed some bugs and improved user experience +- - Values in the main window don’t change after changing default settings +- - Modifying default unit automatically updates the main window default unit +- - Unable to open the "Decoupling UI and program" developer option +- - Program crashes if total click duration exceeds 1.8e308 days +- Removed the “Quick Set Delay” interface as it wasn’t very useful +- [build] Upgraded the build system +- - Merged API, main library, and Cython version files +- - Updated pyd version implementation, allowing you to create pyd files for your Python version +- - Changed the location of runhook, moved it to the root directory +- - Moved git clean into runhook +- Added documentation for multiple prophecy versions + +## Download Link +[Click to Download](https://github.com/xystudiocode/pyClickMouse/releases/tag/3.0.0.23alpha6) \ No newline at end of file diff --git a/documents/public/imgs/features/en/dark/settings/clicker.png b/documents/public/imgs/features/en/dark/settings/clicker.png index 08f60828..0842bd9b 100644 Binary files a/documents/public/imgs/features/en/dark/settings/clicker.png and b/documents/public/imgs/features/en/dark/settings/clicker.png differ diff --git a/documents/public/imgs/features/en/light/settings/clicker.png b/documents/public/imgs/features/en/light/settings/clicker.png index fe362be6..6b3166fe 100644 Binary files a/documents/public/imgs/features/en/light/settings/clicker.png and b/documents/public/imgs/features/en/light/settings/clicker.png differ diff --git a/documents/public/imgs/features/zh-CN/dark/settings/clicker.png b/documents/public/imgs/features/zh-CN/dark/settings/clicker.png index 309f2316..8878cd1c 100644 Binary files a/documents/public/imgs/features/zh-CN/dark/settings/clicker.png and b/documents/public/imgs/features/zh-CN/dark/settings/clicker.png differ diff --git a/documents/public/imgs/features/zh-CN/light/settings/clicker.png b/documents/public/imgs/features/zh-CN/light/settings/clicker.png index d362d641..b807938f 100644 Binary files a/documents/public/imgs/features/zh-CN/light/settings/clicker.png and b/documents/public/imgs/features/zh-CN/light/settings/clicker.png differ diff --git a/documents/zh-CN/features/settings.md b/documents/zh-CN/features/settings.md index 06b75239..bdc5c219 100644 --- a/documents/zh-CN/features/settings.md +++ b/documents/zh-CN/features/settings.md @@ -322,6 +322,14 @@ layout: doc 如果关闭此选项,只有在连点为空的时候才会使用默认值;开启后只要输入格式错误就会使用默认值。 +- 使用默认值修改连点属性:如果启用,那么在一个输入框有值时,另一个无值的输入框会继续沿用。 +- - 类型:开关 +- - 默认值:关 +- - 字段名:`modify_using_default_input` +- 使用默认值修改连点单位:如果启用,那么在单位变化时,默认的数值会继续沿用。 +- - 类型:开关 +- - 默认值:关 +- - 字段名:`modify_using_default_combo` - 连点次数默认值:控制连点次数为空的时候的默认次数 - - 类型:输入框 - - 默认值:空 @@ -405,35 +413,31 @@ layout: doc - - 类型:开关 - - 默认值:开 -- - 字段名:`hotkey_enabled` +- - 字段名:`hotkey,hotkey_enabled` - 左键连点热键:设置左键连点的热键。 - - 类型:输入框 - - 默认值:`F2` -- - 字段名:`left_click_hotkey` +- - 字段名:`hotkey,hotkeys,left_click_hotkey` - 右键连点热键:设置右键连点的热键。 - - 类型:输入框 - - 默认值:`F3` -- - 字段名:`right_click_hotkey` +- - 字段名:`hotkey,hotkeys,right_click_hotkey` - 暂停/重启连点热键:设置暂停/重启连点器的热键。 - - 类型:输入框 - - 默认值:`F4` -- - 字段名:`pause_click_hotkey` +- - 字段名:`hotkey,hotkeys,pause_click_hotkey` - 停止连点热键:设置停止连点的热键。 - - 类型:输入框 - - 默认值:`F6` -- - 字段名:`stop_click_hotkey` +- - 字段名:`hotkey,hotkeys,stop_click_hotkey` - 连点属性热键:设置打开连点属性的热键。 - - 类型:输入框 - - 默认值:`Ctrl+Alt+A` -- - 字段名:`click_attr_hotkey` -- 快速连点热键:设置打开快速连点的热键。 -- - 类型:输入框 -- - 默认值:`Ctrl+Alt+F` -- - 字段名:`fast_click_hotkey` +- - 字段名:`hotkey,hotkeys,click_attr_hotkey` - 主窗口热键:设置打开主窗口的热键。 - - 类型:输入框 - - 默认值:`Ctrl+Alt+M` -- - 字段名:`main_window_hotkey` +- - 字段名:`hotkey,hotkeys,main_window_hotkey` - 重置左键连点设置:用于恢复默认左键连点设置。 - - 类型:按钮 - 重置右键连点设置:用于恢复默认右键连点设置。 diff --git a/documents/zh-CN/updatelog/beta/3/33023a6.md b/documents/zh-CN/updatelog/beta/3/33023a6.md new file mode 100644 index 00000000..c701c461 --- /dev/null +++ b/documents/zh-CN/updatelog/beta/3/33023a6.md @@ -0,0 +1,32 @@ +--- +title: 3.0.0.11alpha2 +layout: doc +--- + +# 3.0.0.11alpha2 + +发布日期:2026/06/27 + +> 这是一个开发测试版更新,为`3.3.0`的开发周期的第5个版本。 + +## 更新内容 +- [refactor]升级日志系统,更详细 +- [refactot]替换部分ui,强制新版ui +- [feat]添两个连点器新设置: +- - 更换部分输入框内容不影响计算 +- - 更换部分单位不影响计算 +- [fix]修复部分bug,优化用户体验 +- - 更改默认设置值后,主窗口的值不变 +- - 修改默认值单位自动更新主窗口的默认值单位 +- - 无法打开"Decoupling UI and program"这个开发项 +- - 当连点总耗时大于1.8e308天时候程序会崩溃 +- 移除"快速设置延迟"界面,因为没有太大用 +- [build]升级编译系统 +- - 合并api、主库和cython版本的文件 +- - 修改了pyd的版本实现,可以为你的python版本制作自己的pyd文件 +- - 修改了runhook的位置,移动到根目录 +- - 将git clean移动到runhook +- 添加多预言版本的文档 + +## 下载链接 +[点击下载](https://github.com/xystudiocode/pyClickMouse/releases/tag/3.0.0.23alpha6) diff --git a/guiclean/txtinfo.py b/guiclean/txtinfo.py index b76f9e05..929a10c4 100644 --- a/guiclean/txtinfo.py +++ b/guiclean/txtinfo.py @@ -16,7 +16,6 @@ class SettingText: pause_click_hotkey = 'pause_click_hotkey' stop_click_hotkey ='stop_click_hotkey' click_attr_hotkey = 'click_attr_hotkey' - fast_click_hotkey = 'fast_click_hotkey' main_window_hotkey = 'main_window_hotkey' default_doc_link = 'default_doc_link' hotkey_enabled = 'hotkey_enabled' @@ -91,10 +90,6 @@ def stop_click_hotkey(self): def click_attr_hotkey(self): return self[SettingText.click_attr_hotkey] - @property - def fast_click_hotkey(self): - return self[SettingText.fast_click_hotkey] - @property def main_window_hotkey(self): return self[SettingText.main_window_hotkey] diff --git a/hooks/gitclean.py b/hooks/gitclean.py new file mode 100644 index 00000000..79036610 --- /dev/null +++ b/hooks/gitclean.py @@ -0,0 +1,3 @@ +import os + +os.system("git gc --aggressive --prune=now") \ No newline at end of file diff --git a/Gui/hooks/hotkey.py b/hooks/hotkey.py similarity index 100% rename from Gui/hooks/hotkey.py rename to hooks/hotkey.py diff --git a/Gui/hooks/keycrypter.py b/hooks/keycrypter.py similarity index 100% rename from Gui/hooks/keycrypter.py rename to hooks/keycrypter.py diff --git a/Gui/hooks/light_color.py b/hooks/light_color.py similarity index 100% rename from Gui/hooks/light_color.py rename to hooks/light_color.py diff --git a/Gui/hooks/ramdom_str.py b/hooks/ramdom_str.py similarity index 100% rename from Gui/hooks/ramdom_str.py rename to hooks/ramdom_str.py diff --git a/Gui/hooks/zip_json.py b/hooks/zip_json.py similarity index 100% rename from Gui/hooks/zip_json.py rename to hooks/zip_json.py diff --git a/makefile b/makefile index a0eebd5b..00ce1da7 100644 --- a/makefile +++ b/makefile @@ -4,8 +4,8 @@ main: echo Please run a build command, such as "make clickmouse". clickmouse: gui/main.py - $(command) --file-description="Clickmouse" --product-name="ClickMouse" --windows-icon-from-ico=gui/res/icons/clickmouse/icon.ico --include-data-dir=gui/res/=res/ --include-data-file=gui/key=key gui/main.py --file-version="3.3.0.22" --enable-plugin=pyside6 --windows-console-mode="disable" --include-data-file=gui/7z.exe=7z.exe --include-data-file=gui/7z.dll=7z.dll --output-dir=dist/clickmouse/ - $(command) --file-description="Clickmouse uninstall" --product-name="uninstall" --windows-icon-from-ico=gui/res/icons/clickmouse/uninstall.ico --file-version="2026.05.23.1" gui/uninstall.py --enable-plugin=pyside6 --windows-console-mode="disable" --windows-uac-admin --output-dir=dist/clickmouse/ + $(command) --file-description="Clickmouse" --product-name="ClickMouse" --windows-icon-from-ico=gui/res/icons/clickmouse/icon.ico --include-data-dir=gui/res/=res/ --include-data-file=gui/key=key gui/main.py --file-version="3.3.0.23" --windows-console-mode="disable" --enable-plugin=pyside6 --include-data-file=gui/7z.exe=7z.exe --include-data-file=gui/7z.dll=7z.dll --output-dir=dist/clickmouse/ + $(command) --file-description="Clickmouse uninstall" --product-name="uninstall" --windows-icon-from-ico=gui/res/icons/clickmouse/uninstall.ico --file-version="2026.06.27.1" gui/uninstall.py --enable-plugin=pyside6 --windows-console-mode="disable" --windows-uac-admin --output-dir=dist/clickmouse/ $(command) --file-description="Clickmouse IPK" --product-name="CmIPK" --windows-icon-from-ico=gui/res/icons/clickmouse/init.ico --file-version="2026.05.23.1" gui/install_pack.py --enable-plugin=pyside6 --windows-console-mode="disable" --output-dir=dist/clickmouse/ $(command) --file-description="Clickmouse repair" --product-name="CmRepair" --windows-icon-from-ico=gui/res/icons/clickmouse/repair.ico --file-version="2026.05.23.1" gui/repair.py --enable-plugin=pyside6 --windows-console-mode="disable" --windows-uac-admin --output-dir=dist/clickmouse/ $(command) --file-version="2026.05.23.1" gui/check_reg_ver.py --windows-console-mode="disable" --output-dir=dist/clickmouse/ @@ -25,25 +25,7 @@ extension: echo No extension! powershell -ExecutionPolicy Bypass -Command "./merge-distFolders.ps1 -SourcePath ./dist/clickmouse/" -clean_pyd: - del -s -q -f build\ clickmouse.egg-info cython\*.c - -gitclean: - git gc --aggressive --prune=now - pyd: @echo off - setlocal enabledelayedexpansion - - set items="38" "39" "310" "311" "312" "313" "314" - - for %%i in (%items%) do ( - "C:\Program Files\Python%%i\python.exe" cython\setup.py build_ext --inplace - ) - set versions=3.13 3.14 - for %%v in (%versions%) do ( - set "orig=%%v" - set "nodot=!orig:.=!" - "C:\Program Files\Python!nodot!\python%!orig!t.exe" cython\setup.py build_ext --inplace - ) \ No newline at end of file + ./mkpyd.bat \ No newline at end of file diff --git a/mkpyd.bat b/mkpyd.bat new file mode 100644 index 00000000..a18e3f8e --- /dev/null +++ b/mkpyd.bat @@ -0,0 +1,27 @@ +@echo off +setlocal enabledelayedexpansion + +set versions=3.8 3.9 3.10 3.11 3.12 3.13 3.14 3.15 3.13t 3.14t 3.15t +set setupPath=setup.py +set pydPath="src\clickmouse\" + +for %%i in (%versions%) do ( + py -V:%%i %setupPath% build_ext --inplace || echo "Python %%i is not installed." +) + +del build\ clickmouse.egg-info %pydPath%\*.c /s /q /f + +set "merged=" + +:: 处理第一个列表 +for %%a in (%versions%) do ( + set "item=%%a" + set "item=!item:.=!" + set "merged=!merged! !item!" +) + +echo Merged items: !merged! + +for %%i in (%merged%) do ( + ren %pydPath%__init__.cp%%i-win_amd64.pyd clickmouse.cp%%i-win_amd64.pyd || echo Renaming failed for __init__.cp%%i-win_amd64.pyd. +) \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index 1d7e1dec..00000000 --- a/pyproject.toml +++ /dev/null @@ -1,28 +0,0 @@ -[build-system] -requires = ["setuptools", "wheel"] -build-backend = "setuptools.build_meta" - -[project] -name = "ClickMouse" -version = "2.1.0" -authors = [ - {name = "xystudio", email = "173288240@qq.com"}, -] -description = "基于Python的鼠标连点工具" -readme = "README.md" -requires-python = ">=3.8" -keywords = ["mouse", "click", "automation", "clickmouse"] -classifiers = [ - "Programming Language :: Python :: 3", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", -] -dependencies = [ - "pyautogui", -] - -[project.scripts] -pyplus = "pyplus_cmd:main" - -[project.urls] -homepage = "https://github.com/xystudiocode/pyClickMouse" \ No newline at end of file diff --git a/Gui/runhook.bat b/runhook.bat similarity index 100% rename from Gui/runhook.bat rename to runhook.bat diff --git a/setup.py b/setup.py index 9317880b..d42276f6 100644 --- a/setup.py +++ b/setup.py @@ -6,18 +6,20 @@ # 以下为setup.py文件内容 # 导入setuptools模块 import setuptools +from Cython.Build import cythonize # 定义setup函数 setuptools.setup( name="ClickMouse", # 包名 - version="2.1.1", # 版本号 + version="3.3.0", # 版本号 + package_dir={"": "src"}, + packages=setuptools.find_packages(where="src"), author="xystudio", # 作者 author_email="173288240@qq.com", # 作者邮箱 description="基于Python的鼠标连点工具", # 包描述 url="https://github.com/xystudiocode/pyClickMouse", # 包的github地址 - long_description=open("README.md", "r", encoding="utf-8").read(), # 包的readme文件 + long_description=open("README-py.md", "r", encoding="utf-8").read(), # 包的readme文件 long_description_content_type="text/markdown", # 指定readme文件格式为markdown - packages=setuptools.find_packages(), # 包的目录结构 classifiers=[ # 包的分类列表 "Programming Language :: Python :: 3", # python版本 "License :: OSI Approved :: MIT License", # 许可证 @@ -29,9 +31,8 @@ entry_points={ # 脚本入口 "console_scripts": [ "clickmouse = clickmouse.__main__:main", + "clickmouse-apiCli = clickmouse_api.__main__:main" ] }, - extras_require = { - "api": ["clickmouse-api"] - } + ext_modules=cythonize(["src/clickmouse/__init__.py"]), # 编译main.py为.pyd文件 ) diff --git a/clickmouse/__init__.py b/src/clickmouse/__init__.py similarity index 100% rename from clickmouse/__init__.py rename to src/clickmouse/__init__.py diff --git a/clickmouse/__main__.py b/src/clickmouse/__main__.py similarity index 100% rename from clickmouse/__main__.py rename to src/clickmouse/__main__.py diff --git a/clickmouse/command_tools.py b/src/clickmouse/command_tools.py similarity index 100% rename from clickmouse/command_tools.py rename to src/clickmouse/command_tools.py diff --git a/clickmouse/version.py b/src/clickmouse/version.py similarity index 100% rename from clickmouse/version.py rename to src/clickmouse/version.py diff --git a/src/clickmouse_api/GUI/__init__.py b/src/clickmouse_api/GUI/__init__.py new file mode 100644 index 00000000..1c98b718 --- /dev/null +++ b/src/clickmouse_api/GUI/__init__.py @@ -0,0 +1,5 @@ +from PySide6.QtWidgets import * +from PySide6.QtCore import * +from PySide6.QtGui import * +import sys +from clickmouse_API.GUI import styles diff --git a/clickmouse_api/clickmouse_API/GUI/styles.py b/src/clickmouse_api/GUI/styles.py similarity index 100% rename from clickmouse_api/clickmouse_API/GUI/styles.py rename to src/clickmouse_api/GUI/styles.py diff --git a/clickmouse_api/clickmouse_API/__init__.py b/src/clickmouse_api/__init__.py similarity index 100% rename from clickmouse_api/clickmouse_API/__init__.py rename to src/clickmouse_api/__init__.py diff --git a/clickmouse_api/clickmouse_API/__main__.py b/src/clickmouse_api/__main__.py similarity index 100% rename from clickmouse_api/clickmouse_API/__main__.py rename to src/clickmouse_api/__main__.py diff --git a/clickmouse_api/api_package_format.json b/src/clickmouse_api/api_package_format.json similarity index 100% rename from clickmouse_api/api_package_format.json rename to src/clickmouse_api/api_package_format.json diff --git a/src/clickmouse_api/command.py b/src/clickmouse_api/command.py new file mode 100644 index 00000000..e69de29b diff --git a/src/clickmouse_api/install/install.bat b/src/clickmouse_api/install/install.bat new file mode 100644 index 00000000..e69de29b diff --git a/src/clickmouse_api/install/requirements.txt b/src/clickmouse_api/install/requirements.txt new file mode 100644 index 00000000..bacf386d --- /dev/null +++ b/src/clickmouse_api/install/requirements.txt @@ -0,0 +1,6 @@ +PySide6 >=6.10 +Nuitka >=4 +PyAutogui +uiml>=0.2 +pynput>=1 +pywin32>=303 \ No newline at end of file diff --git a/clickmouse_api/clickmouse_API/packageParser/__init__.py b/src/clickmouse_api/packageParser/__init__.py similarity index 100% rename from clickmouse_api/clickmouse_API/packageParser/__init__.py rename to src/clickmouse_api/packageParser/__init__.py