From 63a24f7d15d95e8172cfdda36adf2a582b51e68c Mon Sep 17 00:00:00 2001
From: xystudio <173288240@qq.com>
Date: Sat, 27 Jun 2026 21:34:06 +0800
Subject: [PATCH] =?UTF-8?q?feat(clickmouse)=203.3.0.23alpha6:=E5=8D=87?=
=?UTF-8?q?=E7=BA=A7=E6=97=A5=E5=BF=97=EF=BC=8C=E4=BC=98=E5=8C=96=E8=BE=93?=
=?UTF-8?q?=E5=85=A5=E6=A1=86=E4=BD=93=E9=AA=8C=EF=BC=8C=E7=A7=BB=E9=99=A4?=
=?UTF-8?q?"=E5=BF=AB=E9=80=9F=E8=AE=BE=E7=BD=AE"=E7=95=8C=E9=9D=A2?=
=?UTF-8?q?=EF=BC=8C=E6=B7=BB=E5=8A=A0=E4=B8=A4=E4=B8=AA=E6=96=B0=E7=9A=84?=
=?UTF-8?q?=E8=AE=BE=E7=BD=AE=EF=BC=8C=E9=83=A8=E5=88=86=E5=86=85=E5=AE=B9?=
=?UTF-8?q?=E5=AE=8C=E5=85=A8=E9=87=8D=E6=9E=84=20-=20[refactor]=E5=8D=87?=
=?UTF-8?q?=E7=BA=A7=E6=97=A5=E5=BF=97=E7=B3=BB=E7=BB=9F=EF=BC=8C=E6=9B=B4?=
=?UTF-8?q?=E8=AF=A6=E7=BB=86=20-=20[refactot]=E6=9B=BF=E6=8D=A2=E9=83=A8?=
=?UTF-8?q?=E5=88=86ui=EF=BC=8C=E5=BC=BA=E5=88=B6=E6=96=B0=E7=89=88ui=20-?=
=?UTF-8?q?=20[feat]=E6=B7=BB=E4=B8=A4=E4=B8=AA=E8=BF=9E=E7=82=B9=E5=99=A8?=
=?UTF-8?q?=E6=96=B0=E8=AE=BE=E7=BD=AE=EF=BC=9A=20-=20-=20=E6=9B=B4?=
=?UTF-8?q?=E6=8D=A2=E9=83=A8=E5=88=86=E8=BE=93=E5=85=A5=E6=A1=86=E5=86=85?=
=?UTF-8?q?=E5=AE=B9=E4=B8=8D=E5=BD=B1=E5=93=8D=E8=AE=A1=E7=AE=97=20-=20-?=
=?UTF-8?q?=20=E6=9B=B4=E6=8D=A2=E9=83=A8=E5=88=86=E5=8D=95=E4=BD=8D?=
=?UTF-8?q?=E4=B8=8D=E5=BD=B1=E5=93=8D=E8=AE=A1=E7=AE=97=20-=20[fix]?=
=?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=83=A8=E5=88=86bug=EF=BC=8C=E4=BC=98?=
=?UTF-8?q?=E5=8C=96=E7=94=A8=E6=88=B7=E4=BD=93=E9=AA=8C=20-=20-=20?=
=?UTF-8?q?=E6=9B=B4=E6=94=B9=E9=BB=98=E8=AE=A4=E8=AE=BE=E7=BD=AE=E5=80=BC?=
=?UTF-8?q?=E5=90=8E=EF=BC=8C=E4=B8=BB=E7=AA=97=E5=8F=A3=E7=9A=84=E5=80=BC?=
=?UTF-8?q?=E4=B8=8D=E5=8F=98=20-=20-=20=E4=BF=AE=E6=94=B9=E9=BB=98?=
=?UTF-8?q?=E8=AE=A4=E5=80=BC=E5=8D=95=E4=BD=8D=E8=87=AA=E5=8A=A8=E6=9B=B4?=
=?UTF-8?q?=E6=96=B0=E4=B8=BB=E7=AA=97=E5=8F=A3=E7=9A=84=E9=BB=98=E8=AE=A4?=
=?UTF-8?q?=E5=80=BC=E5=8D=95=E4=BD=8D=20-=20=E7=A7=BB=E9=99=A4"=E5=BF=AB?=
=?UTF-8?q?=E9=80=9F=E8=AE=BE=E7=BD=AE=E5=BB=B6=E8=BF=9F"=E7=95=8C?=
=?UTF-8?q?=E9=9D=A2=EF=BC=8C=E5=9B=A0=E4=B8=BA=E6=B2=A1=E6=9C=89=E5=A4=AA?=
=?UTF-8?q?=E5=A4=A7=E7=94=A8=20-=20[build]=E5=8D=87=E7=BA=A7=E7=BC=96?=
=?UTF-8?q?=E8=AF=91=E7=B3=BB=E7=BB=9F=20-=20-=20=E5=90=88=E5=B9=B6api?=
=?UTF-8?q?=E3=80=81=E4=B8=BB=E5=BA=93=E5=92=8Ccython=E7=89=88=E6=9C=AC?=
=?UTF-8?q?=E7=9A=84=E6=96=87=E4=BB=B6=20-=20-=20=E4=BF=AE=E6=94=B9?=
=?UTF-8?q?=E4=BA=86pyd=E7=9A=84=E7=89=88=E6=9C=AC=E5=AE=9E=E7=8E=B0?=
=?UTF-8?q?=EF=BC=8C=E5=8F=AF=E4=BB=A5=E4=B8=BA=E4=BD=A0=E7=9A=84python?=
=?UTF-8?q?=E7=89=88=E6=9C=AC=E5=88=B6=E4=BD=9C=E8=87=AA=E5=B7=B1=E7=9A=84?=
=?UTF-8?q?pyd=E6=96=87=E4=BB=B6=20-=20-=20=E4=BF=AE=E6=94=B9=E4=BA=86runh?=
=?UTF-8?q?ook=E7=9A=84=E4=BD=8D=E7=BD=AE=EF=BC=8C=E7=A7=BB=E5=8A=A8?=
=?UTF-8?q?=E5=88=B0=E6=A0=B9=E7=9B=AE=E5=BD=95=20-=20-=20=E5=B0=86git=20c?=
=?UTF-8?q?lean=E7=A7=BB=E5=8A=A8=E5=88=B0runhook=20-=20=E6=B7=BB=E5=8A=A0?=
=?UTF-8?q?=E5=A4=9A=E9=A2=84=E8=A8=80=E7=89=88=E6=9C=AC=E7=9A=84=E6=96=87?=
=?UTF-8?q?=E6=A1=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.gitignore | 8 +-
Gui/install_pack.py | 12 +-
Gui/logger.py | 32 +-
Gui/main.py | 1658 +++++------------
Gui/parse_dev.py | 31 -
Gui/res/defaultsetting.json | 48 +-
Gui/res/dev_data.json | 2 -
Gui/res/langs/langs.json | 18 +-
Gui/res/styles/dark.qss | 2 +-
Gui/res/styles/light.qss | 2 +-
Gui/res/ui/clickattr.gui | 6 +-
Gui/res/ui/fastClick.gui | 44 -
Gui/res/ui/settings/clicker.gui | 25 +-
Gui/sharelibs.py | 66 +-
Gui/tests/test_nest.py | 45 +
Gui/txtinfo.py | 57 +-
Gui/uiStyles/widgets.py | 223 ++-
.../clickmouse_API/command.py => MANIFEST.in | 0
README-py.md | 18 +
clickmouse_api/README.md | 28 -
clickmouse_api/clickmouse_API/GUI/__init__.py | 2 -
clickmouse_api/pyproject.toml | 32 -
clickmouse_api/setup.py | 37 -
cython/main.py | 16 -
cython/setup.py | 12 -
documents/.vitepress/config.js | 8 +
documents/en/features/settings.md | 27 +-
documents/en/updatelog/beta/3/33023a6.md | 32 +
.../features/en/dark/settings/clicker.png | Bin 24113 -> 425614 bytes
.../features/en/light/settings/clicker.png | Bin 23384 -> 420109 bytes
.../features/zh-CN/dark/settings/clicker.png | Bin 27087 -> 44523 bytes
.../features/zh-CN/light/settings/clicker.png | Bin 25929 -> 42086 bytes
documents/zh-CN/features/settings.md | 26 +-
documents/zh-CN/updatelog/beta/3/33023a6.md | 32 +
guiclean/txtinfo.py | 5 -
hooks/gitclean.py | 3 +
{Gui/hooks => hooks}/hotkey.py | 0
{Gui/hooks => hooks}/keycrypter.py | 0
{Gui/hooks => hooks}/light_color.py | 0
{Gui/hooks => hooks}/ramdom_str.py | 0
{Gui/hooks => hooks}/zip_json.py | 0
makefile | 24 +-
mkpyd.bat | 27 +
pyproject.toml | 28 -
Gui/runhook.bat => runhook.bat | 0
setup.py | 13 +-
{clickmouse => src/clickmouse}/__init__.py | 0
{clickmouse => src/clickmouse}/__main__.py | 0
.../clickmouse}/command_tools.py | 0
{clickmouse => src/clickmouse}/version.py | 0
src/clickmouse_api/GUI/__init__.py | 5 +
.../clickmouse_api}/GUI/styles.py | 0
.../clickmouse_api}/__init__.py | 0
.../clickmouse_api}/__main__.py | 0
.../clickmouse_api}/api_package_format.json | 0
src/clickmouse_api/command.py | 0
src/clickmouse_api/install/install.bat | 0
src/clickmouse_api/install/requirements.txt | 6 +
.../clickmouse_api}/packageParser/__init__.py | 0
59 files changed, 979 insertions(+), 1681 deletions(-)
delete mode 100644 Gui/parse_dev.py
delete mode 100644 Gui/res/dev_data.json
delete mode 100644 Gui/res/ui/fastClick.gui
create mode 100644 Gui/tests/test_nest.py
rename clickmouse_api/clickmouse_API/command.py => MANIFEST.in (100%)
create mode 100644 README-py.md
delete mode 100644 clickmouse_api/README.md
delete mode 100644 clickmouse_api/clickmouse_API/GUI/__init__.py
delete mode 100644 clickmouse_api/pyproject.toml
delete mode 100644 clickmouse_api/setup.py
delete mode 100644 cython/main.py
delete mode 100644 cython/setup.py
create mode 100644 documents/en/updatelog/beta/3/33023a6.md
create mode 100644 documents/zh-CN/updatelog/beta/3/33023a6.md
create mode 100644 hooks/gitclean.py
rename {Gui/hooks => hooks}/hotkey.py (100%)
rename {Gui/hooks => hooks}/keycrypter.py (100%)
rename {Gui/hooks => hooks}/light_color.py (100%)
rename {Gui/hooks => hooks}/ramdom_str.py (100%)
rename {Gui/hooks => hooks}/zip_json.py (100%)
create mode 100644 mkpyd.bat
delete mode 100644 pyproject.toml
rename Gui/runhook.bat => runhook.bat (100%)
rename {clickmouse => src/clickmouse}/__init__.py (100%)
rename {clickmouse => src/clickmouse}/__main__.py (100%)
rename {clickmouse => src/clickmouse}/command_tools.py (100%)
rename {clickmouse => src/clickmouse}/version.py (100%)
create mode 100644 src/clickmouse_api/GUI/__init__.py
rename {clickmouse_api/clickmouse_API => src/clickmouse_api}/GUI/styles.py (100%)
rename {clickmouse_api/clickmouse_API => src/clickmouse_api}/__init__.py (100%)
rename {clickmouse_api/clickmouse_API => src/clickmouse_api}/__main__.py (100%)
rename {clickmouse_api => src/clickmouse_api}/api_package_format.json (100%)
create mode 100644 src/clickmouse_api/command.py
create mode 100644 src/clickmouse_api/install/install.bat
create mode 100644 src/clickmouse_api/install/requirements.txt
rename {clickmouse_api/clickmouse_API => src/clickmouse_api}/packageParser/__init__.py (100%)
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 08f6082833e9d2fd8d35d0fc793259413f274a02..0842bd9b57bccc3fbd7ff7d77ce0780c33413999 100644
GIT binary patch
literal 425614
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