From 0a8763add5cd825cfa4293a8051b9bf425483d70 Mon Sep 17 00:00:00 2001
From: KenanZhu <3471685733@qq.com>
Date: Sat, 29 Nov 2025 20:03:45 +0800
Subject: [PATCH] feat(gui): breaking changes - Timer Task Management
1. we add menu actions 'manual' and 'about', so
you can click actions to open manual and about dialog.
2. we introduce timer task management feature, so
you can add, delete timer tasks to auto run task.
3. other style improvement in gui...
---
.gitignore | 4 +
src/gui/ALAboutDialog.py | 141 ++++++++++++++
src/gui/ALAboutDialog.ui | 140 ++++++++++++++
src/gui/ALAddTimerTaskDialog.py | 148 +++++++++++++++
src/gui/ALAddTimerTaskDialog.ui | 249 +++++++++++++++++++++++++
src/gui/ALConfigWidget.ui | 9 +
src/gui/ALMainWindow.py | 245 +++++++++++++++++++++++--
src/gui/ALMainWindow.ui | 40 +++-
src/gui/ALTimerTaskWidget.py | 313 ++++++++++++++++++++++++++++++++
src/gui/ALTimerTaskWidget.ui | 239 ++++++++++++++++++++++++
src/gui/AppInfo.py | 1 +
11 files changed, 1513 insertions(+), 16 deletions(-)
create mode 100644 src/gui/ALAboutDialog.py
create mode 100644 src/gui/ALAboutDialog.ui
create mode 100644 src/gui/ALAddTimerTaskDialog.py
create mode 100644 src/gui/ALAddTimerTaskDialog.ui
create mode 100644 src/gui/ALTimerTaskWidget.py
create mode 100644 src/gui/ALTimerTaskWidget.ui
create mode 100644 src/gui/AppInfo.py
diff --git a/.gitignore b/.gitignore
index c676a0b..76be2ea 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,4 +14,8 @@ src/gui/AutoLibraryResources.py
src/gui/AutoLibraryResource.py
src/gui/Ui_ALMainWindow.py
src/gui/Ui_ALConfigWidget.py
+src/gui/Ui_ALTimerTaskWidget.py
+src/gui/Ui_ALAddTimerTaskDialog.py
+src/gui/Ui_ALAboutDialog.py
+
Main.spec
diff --git a/src/gui/ALAboutDialog.py b/src/gui/ALAboutDialog.py
new file mode 100644
index 0000000..7a52e94
--- /dev/null
+++ b/src/gui/ALAboutDialog.py
@@ -0,0 +1,141 @@
+# -*- coding: utf-8 -*-
+"""
+Copyright (c) 2025 KenanZhu.
+All rights reserved.
+
+This software is provided "as is", without any warranty of any kind.
+You may use, modify, and distribute this file under the terms of the MIT License.
+See the LICENSE file for details.
+"""
+import sys
+import platform
+
+from PySide6.QtGui import (
+ QIcon
+)
+from PySide6.QtWidgets import (
+ QDialog, QApplication
+)
+from PySide6.QtCore import (
+ QTimer, Qt
+)
+
+from gui.AppInfo import AL_VERSION
+from gui.Ui_ALAboutDialog import Ui_ALAboutDialog
+
+from gui import AutoLibraryResource
+
+
+class ALAboutDialog(QDialog, Ui_ALAboutDialog):
+
+ def __init__(
+ self,
+ parent=None
+ ):
+ super().__init__(parent)
+
+ self.setupUi(self)
+ self.modifyUi()
+ self.connectSignals()
+
+
+ def modifyUi(
+ self
+ ):
+
+ self.LogoIconLabel.setPixmap(QIcon(":/res/icon/icons/AutoLibrary.ico").pixmap(48, 48))
+ info_text = self.generateAboutText()
+ self.AboutInfoEdit.setHtml(info_text)
+ self.AboutInfoEdit.setTextInteractionFlags(Qt.TextBrowserInteraction)
+
+
+ def connectSignals(
+ self
+ ):
+
+ self.CopyButton.clicked.connect(self.copyAboutInfo)
+
+
+ def generateAboutText(
+ self
+ ):
+
+ os_info = self.getOSInfo()
+ about_text = f"""
+
Version Information:
+Version: {AL_VERSION}
+Python version: {platform.python_version()}
+Qt version: {self.getQtVersion()}
+
+Author Information:
+Developer: KenanZhu
+Contact: nanoki_zh@163.com
+GitHub: https://www.github.com/KenanZhu
+
+Project Information:
+License: MIT License
+Project repository: https://www.github.com/KenanZhu/AutoLibrary
+Project website: https://www.autolibrary.cv/
+
+System Information:
+Processor: {platform.processor()}
+Operating system: {os_info['system']}
+System version: {os_info['version']}
+System architecture: {os_info['architecture']}
+"""
+ return about_text
+
+
+ def getOSInfo(
+ self
+ ):
+
+ system = platform.system()
+ version = platform.version()
+ architecture = platform.architecture()[0]
+
+ if system == "Windows":
+ try:
+ version = platform.win32_ver()[1]
+ except:
+ pass
+ elif system == "Darwin":
+ try:
+ version = platform.mac_ver()[0]
+ except:
+ pass
+ elif system == "Linux":
+ try:
+ import distro # try to get Linux distro info
+ version = f"{distro.name()} {distro.version()}"
+ except ImportError:
+ pass
+
+ return {
+ 'system': system,
+ 'version': version,
+ 'architecture': architecture
+ }
+
+
+ def getQtVersion(
+ self
+ ):
+
+ try:
+ from PySide6.QtCore import qVersion
+ return qVersion()
+ except:
+ return "Unknown"
+
+
+ def copyAboutInfo(
+ self
+ ):
+
+ about_text = self.AboutInfoEdit.toPlainText()
+ clipboard = QApplication.clipboard()
+ clipboard.setText(about_text)
+ original_text = self.CopyButton.text()
+ self.CopyButton.setText("已复制")
+ QTimer.singleShot(2000, lambda: self.CopyButton.setText(original_text))
\ No newline at end of file
diff --git a/src/gui/ALAboutDialog.ui b/src/gui/ALAboutDialog.ui
new file mode 100644
index 0000000..74b2b9c
--- /dev/null
+++ b/src/gui/ALAboutDialog.ui
@@ -0,0 +1,140 @@
+
+
+ ALAboutDialog
+
+
+
+ 0
+ 0
+ 300
+ 300
+
+
+
+
+ 300
+ 300
+
+
+
+
+ 800
+ 300
+
+
+
+ 关于 - AutoLibrary
+
+
+
+ 5
+
+
+ 5
+
+
+ 5
+
+
+ 5
+
+
+ 5
+
+ -
+
+
+ 5
+
+
-
+
+
+
+ 56
+ 56
+
+
+
+
+ 56
+ 56
+
+
+
+
+
+
+ true
+
+
+ 0
+
+
+
+ -
+
+
+
+ 24
+ true
+
+
+
+ 0
+
+
+ AutoLibrary
+
+
+ 0
+
+
+ 0
+
+
+
+
+
+ -
+
+
+
+ Courier New
+ false
+
+
+
+ QTextEdit::LineWrapMode::NoWrap
+
+
+ true
+
+
+ Qt::TextInteractionFlag::TextBrowserInteraction
+
+
+
+ -
+
+
+
+ 80
+ 25
+
+
+
+
+ 80
+ 25
+
+
+
+ 复制
+
+
+
+
+
+
+
+
diff --git a/src/gui/ALAddTimerTaskDialog.py b/src/gui/ALAddTimerTaskDialog.py
new file mode 100644
index 0000000..4620a4f
--- /dev/null
+++ b/src/gui/ALAddTimerTaskDialog.py
@@ -0,0 +1,148 @@
+# -*- coding: utf-8 -*-
+"""
+Copyright (c) 2025 KenanZhu.
+All rights reserved.
+
+This software is provided "as is", without any warranty of any kind.
+You may use, modify, and distribute this file under the terms of the MIT License.
+See the LICENSE file for details.
+"""
+import os
+import sys
+import time
+import uuid
+import queue
+
+from enum import Enum
+from datetime import datetime, timedelta
+
+from PySide6.QtCore import (
+ Qt, Signal, Slot, QDateTime
+)
+from PySide6.QtWidgets import (
+ QLabel, QDialog, QWidget, QSpinBox, QVBoxLayout,
+ QHBoxLayout, QGridLayout, QDateTimeEdit
+)
+from PySide6.QtGui import (
+ QCloseEvent
+)
+
+from gui.Ui_ALAddTimerTaskDialog import Ui_ALAddTimerTaskDialog
+
+
+class TimerTaskStatus(Enum):
+ PENDING = "等待中"
+ READY = "已就绪"
+ RUNNING = "执行中"
+ EXECUTED = "已执行"
+ OUTDATED = "已过期"
+
+
+class ALAddTimerTaskWidget(QDialog, Ui_ALAddTimerTaskDialog):
+
+ def __init__(
+ self,
+ parent = None
+ ):
+
+ super().__init__(parent)
+
+ self.setupUi(self)
+ self.connectSignals()
+ self.modifyUi()
+
+
+ def modifyUi(
+ self
+ ):
+
+ self.TimerTypeComboBox.setCurrentIndex(0)
+ self.SpecificTimerWidget = QWidget()
+ self.SpecificTimerLayout = QHBoxLayout(self.SpecificTimerWidget)
+ self.SpecificTimerLayout.addWidget(QLabel("定时时间:"))
+ self.SpecificDateTimeEdit = QDateTimeEdit()
+ self.SpecificDateTimeEdit.setCalendarPopup(True)
+ self.SpecificDateTimeEdit.setDisplayFormat("yyyy-MM-dd HH:mm:ss")
+ self.SpecificDateTimeEdit.setMinimumDateTime(QDateTime.currentDateTime())
+ self.SpecificDateTimeEdit.setDateTime(QDateTime.currentDateTime().addSecs(60))
+ self.SpecificTimerLayout.addWidget(self.SpecificDateTimeEdit)
+ self.TimerConfigLayout.addWidget(self.SpecificTimerWidget)
+
+ self.RelativeTimerWidget = QWidget()
+ self.RelativeTimerLayout = QGridLayout(self.RelativeTimerWidget)
+ self.RelativeTimerLayout.addWidget(QLabel("相对时间:"), 0, 0)
+ self.RelativeDaySpinBox = QSpinBox()
+ self.RelativeDaySpinBox.setMinimum(0)
+ self.RelativeDaySpinBox.setMaximum(365)
+ self.RelativeDaySpinBox.setSuffix("天")
+ self.RelativeTimerLayout.addWidget(self.RelativeDaySpinBox, 1, 0)
+ self.RelativeHourSpinBox = QSpinBox()
+ self.RelativeHourSpinBox.setMinimum(0)
+ self.RelativeHourSpinBox.setMaximum(23)
+ self.RelativeHourSpinBox.setSuffix("时")
+ self.RelativeTimerLayout.addWidget(self.RelativeHourSpinBox, 1, 1)
+ self.RelativeMinuteSpinBox = QSpinBox()
+ self.RelativeMinuteSpinBox.setMinimum(0)
+ self.RelativeMinuteSpinBox.setMaximum(59)
+ self.RelativeMinuteSpinBox.setSuffix("分")
+ self.RelativeTimerLayout.addWidget(self.RelativeMinuteSpinBox, 1, 2)
+ self.RelativeSecondSpinBox = QSpinBox()
+ self.RelativeSecondSpinBox.setMinimum(0)
+ self.RelativeSecondSpinBox.setMaximum(59)
+ self.RelativeSecondSpinBox.setSuffix("秒")
+ self.RelativeTimerLayout.addWidget(self.RelativeSecondSpinBox, 1, 3)
+ self.TimerConfigLayout.addWidget(self.RelativeTimerWidget)
+ self.RelativeTimerWidget.setVisible(False)
+
+
+ def connectSignals(
+ self
+ ):
+
+ self.CancelButton.clicked.connect(self.reject)
+ self.ConfirmButton.clicked.connect(self.accept)
+ self.TimerTypeComboBox.currentIndexChanged.connect(self.onTimerTypeComboBoxIndexChanged)
+
+
+ def getTimerTask(
+ self
+ ) -> dict:
+
+ added_time = datetime.now()
+ if not self.TaskNameLineEdit.text():
+ name = f"未命名任务-{added_time.strftime("%Y%m%d%H%M%S")}"
+ else:
+ name = self.TaskNameLineEdit.text()
+ timer_type_index = self.TimerTypeComboBox.currentIndex()
+ silent = not self.ShowBeforeRunRadioButton.isChecked()
+ if timer_type_index == 0:
+ execute_time = self.SpecificDateTimeEdit.dateTime()
+ tmp_time_str = execute_time.toString("yyyy-MM-dd HH:mm:ss")
+ execute_time = datetime.strptime(tmp_time_str, "%Y-%m-%d %H:%M:%S")
+ else:
+ execute_time = datetime.now() + timedelta(
+ days = self.RelativeDaySpinBox.value(),
+ hours = self.RelativeHourSpinBox.value(),
+ minutes = self.RelativeMinuteSpinBox.value(),
+ seconds = self.RelativeSecondSpinBox.value()
+ )
+ return {
+ "name": name,
+ "task_uuid": uuid.uuid4().hex.upper() + f"-{added_time.strftime("%Y%m%d%H%M%S")}",
+ "time_type": self.TimerTypeComboBox.currentText(),
+ "execute_time": execute_time,
+ "silent": silent,
+ "add_time": added_time,
+ "status": TimerTaskStatus.PENDING,
+ "executed": False
+ }
+
+
+ @Slot(int)
+ def onTimerTypeComboBoxIndexChanged(
+ self,
+ index: int
+ ):
+
+ self.SpecificTimerWidget.setVisible(index == 0)
+ self.RelativeTimerWidget.setVisible(index == 1)
\ No newline at end of file
diff --git a/src/gui/ALAddTimerTaskDialog.ui b/src/gui/ALAddTimerTaskDialog.ui
new file mode 100644
index 0000000..c0ef6db
--- /dev/null
+++ b/src/gui/ALAddTimerTaskDialog.ui
@@ -0,0 +1,249 @@
+
+
+ ALAddTimerTaskDialog
+
+
+
+ 0
+ 0
+ 300
+ 300
+
+
+
+
+ 0
+ 300
+
+
+
+
+ 500
+ 300
+
+
+
+ 添加定时任务 - AutoLibrary
+
+
+ true
+
+
+ true
+
+
+
+ 5
+
+
+ 5
+
+
+ 5
+
+
+ 5
+
+
+ 5
+
+ -
+
+
+ 5
+
+
-
+
+
+
+ 60
+ 25
+
+
+
+
+ 16777215
+ 25
+
+
+
+ 任务名称:
+
+
+
+ -
+
+
+
+
+ -
+
+
+ 定时设置
+
+
+
+ 5
+
+
+ 5
+
+
+ 5
+
+
+ 5
+
+
+ 5
+
+
-
+
+
+ 5
+
+
-
+
+
+ 定时类型:
+
+
+
+ -
+
+
-
+
+ 特定时间
+
+
+ -
+
+ 相对时间
+
+
+
+
+
+
+
+
+
+ -
+
+
+ 运行设置
+
+
+
+ 5
+
+
+ 5
+
+
+ 5
+
+
+ 5
+
+
+ 5
+
+
-
+
+
+ 静默运行
+
+
+ true
+
+
+ true
+
+
+ false
+
+
+ true
+
+
+
+ -
+
+
+ 运行前提示
+
+
+
+
+
+
+ -
+
+
+ 5
+
+
-
+
+
+ QFrame::Shape::NoFrame
+
+
+ QFrame::Shadow::Plain
+
+
+ 0
+
+
+
+ -
+
+
+
+ 80
+ 25
+
+
+
+
+ 80
+ 25
+
+
+
+ 取消
+
+
+
+ -
+
+
+
+ 80
+ 25
+
+
+
+
+ 80
+ 25
+
+
+
+ 添加
+
+
+ true
+
+
+ true
+
+
+
+
+
+
+
+
+
+
diff --git a/src/gui/ALConfigWidget.ui b/src/gui/ALConfigWidget.ui
index 0947db5..e507a52 100644
--- a/src/gui/ALConfigWidget.ui
+++ b/src/gui/ALConfigWidget.ui
@@ -1829,6 +1829,9 @@
导出配置文件
+
+
+
@@ -1924,6 +1927,9 @@
新建配置
+
+
+
-
@@ -1943,6 +1949,9 @@
加载配置
+
+
+
-
diff --git a/src/gui/ALMainWindow.py b/src/gui/ALMainWindow.py
index 466f627..6f2d5dd 100644
--- a/src/gui/ALMainWindow.py
+++ b/src/gui/ALMainWindow.py
@@ -13,19 +13,21 @@ import time
import queue
from PySide6.QtCore import (
- Qt, Signal, Slot, QDir, QFileInfo, QTimer, QThread
+ Qt, Signal, Slot, QDir, QFileInfo, QTimer, QThread, QUrl,
)
from PySide6.QtWidgets import (
- QMainWindow, QMenu
+ QMainWindow, QMenu, QSystemTrayIcon
)
from PySide6.QtGui import (
- QTextCursor, QCloseEvent, QFont, QIcon
+ QTextCursor, QCloseEvent, QFont, QIcon, QDesktopServices
)
-from .Ui_ALMainWindow import Ui_ALMainWindow
-from .ALConfigWidget import ALConfigWidget
+from gui.Ui_ALMainWindow import Ui_ALMainWindow
+from gui.ALConfigWidget import ALConfigWidget
+from gui.ALTimerTaskWidget import ALTimerTaskWidget
+from gui.ALAboutDialog import ALAboutDialog
-from . import AutoLibraryResource
+from gui import AutoLibraryResource
from operators.AutoLib import AutoLib
from utils.ConfigReader import ConfigReader
@@ -49,7 +51,6 @@ class AutoLibWorker(QThread):
self.__input_queue = input_queue
self.__output_queue = output_queue
self.__config_paths = config_paths
- self.__stopped = False
def checkTimeAvailable(
@@ -110,15 +111,46 @@ class AutoLibWorker(QThread):
self.finishedSignal.emit()
- def stop(
+class TimerTaskWorker(AutoLibWorker):
+
+ finishedSignal_TimerWorker = Signal(dict)
+
+ def __init__(
+ self,
+ timer_task: dict,
+ input_queue: queue.Queue,
+ output_queue: queue.Queue,
+ config_paths: dict
+ ):
+
+ super().__init__(
+ input_queue,
+ output_queue,
+ config_paths,
+ )
+
+ self.__timer_task = timer_task
+ self.__stopped = False
+
+ def run(
self
):
- self.__stopped = True
+ self.showTraceSignal.emit(
+ f"定时任务 {self.__timer_task['name']} 开始运行"
+ )
+ super().run()
+ self.showTraceSignal.emit(
+ f"定时任务 {self.__timer_task['name']} 运行结束"
+ )
+ self.finishedSignal_TimerWorker.emit(self.__timer_task)
class ALMainWindow(QMainWindow, Ui_ALMainWindow):
+ timerTaskIsRunning = Signal(dict)
+ timerTaskIsExecuted = Signal(dict)
+
def __init__(
self
):
@@ -129,27 +161,100 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
self.setupUi(self)
self.__input_queue = queue.Queue()
self.__output_queue = queue.Queue()
+ self.__timer_task_queue = queue.Queue()
script_path = sys.executable
script_dir = QFileInfo(script_path).absoluteDir()
self.__config_paths = {
"system": QDir.toNativeSeparators(script_dir.absoluteFilePath("system.json")),
"users": QDir.toNativeSeparators(script_dir.absoluteFilePath("users.json")),
}
+ self.__alTimerTaskWidget = None
self.__alConfigWidget = None
+ self.__alAboutDialog = None
self.__auto_lib_thread = None
+ self.__current_timer_task_thread = None
+ self.__is_running_timer_task = False
self.modifyUi()
+ self.setupTray()
self.connectSignals()
self.startMsgPolling()
+ self.startTimerTaskPolling()
def modifyUi(
self
):
- icon = QIcon(":/res/icon/icons/AutoLibrary.ico")
- self.setWindowIcon(icon)
+ self.icon = QIcon(":/res/icon/icons/AutoLibrary.ico")
+ self.setWindowIcon(self.icon)
self.MessageIOTextEdit.setFont(QFont("Courier New", 10))
+ self.ManualAction.triggered.connect(self.onManualActionTriggered)
+ self.AboutAction.triggered.connect(self.onAboutActionTriggered)
+
+
+ def onAboutActionTriggered(
+ self
+ ):
+
+ if self.__alAboutDialog is None:
+ self.__alAboutDialog = ALAboutDialog(self)
+ self.__alAboutDialog.show()
+
+
+ def onManualActionTriggered(
+ self
+ ):
+
+ url = QUrl("https://www.autolibrary.cv/docs/manual_lists.html")
+ QDesktopServices.openUrl(url)
+
+
+ def setupTray(
+ self
+ ):
+
+ if not QSystemTrayIcon.isSystemTrayAvailable():
+ self.showTraceSignal.emit(
+ "系统不支持系统托盘功能, 无法创建系统托盘图标。"
+ )
+ return
+ self.TrayIcon = QSystemTrayIcon(self.icon, self)
+ self.TrayIcon.setToolTip("AutoLibrary")
+
+ self.TrayMenu = QMenu()
+ self.TrayMenu.addAction("显示主窗口", self.showNormal)
+ self.TrayMenu.addAction("显示定时窗口", self.onTimerTaskWidgetButtonClicked)
+ self.TrayMenu.addAction("最小化到托盘", self.hideToTray)
+ self.TrayMenu.addSeparator()
+ self.TrayMenu.addAction("退出", self.close)
+ self.TrayIcon.setContextMenu(self.TrayMenu)
+
+ self.TrayIcon.setContextMenu(self.TrayMenu)
+ self.TrayIcon.activated.connect(self.onTrayIconActivated)
+ self.TrayIcon.show()
+
+
+ def hideToTray(
+ self
+ ):
+
+ self.hide()
+ self.TrayIcon.showMessage(
+ "AutoLibrary",
+ "\n已最小化到托盘",
+ QSystemTrayIcon.MessageIcon.Information,
+ 2000
+ )
+
+
+ def onTrayIconActivated(
+ self,
+ reason: QSystemTrayIcon.ActivationReason
+ ):
+
+ if reason == QSystemTrayIcon.DoubleClick:
+ self.showNormal()
def connectSignals(
@@ -157,6 +262,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
):
self.ConfigButton.clicked.connect(self.onConfigButtonClicked)
+ self.TimerTaskWidgetButton.clicked.connect(self.onTimerTaskWidgetButtonClicked)
self.StartButton.clicked.connect(self.onStartButtonClicked)
self.StopButton.clicked.connect(self.onStopButtonClicked)
self.SendButton.clicked.connect(self.onSendButtonClicked)
@@ -170,8 +276,17 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
if self.__timer and self.__timer.isActive():
self.__timer.stop()
+ if self.__timer_task_timer and self.__timer_task_timer.isActive():
+ self.__timer_task_timer.stop()
+ if self.__is_running_timer_task:
+ self.__current_timer_task_thread.wait(2000)
+ self.__current_timer_task_thread.deleteLater()
+ if self.__alTimerTaskWidget:
+ self.__alTimerTaskWidget.close()
+ self.__alTimerTaskWidget.deleteLater()
if self.__alConfigWidget:
self.__alConfigWidget.close()
+ self.__alConfigWidget.deleteLater()
super().closeEvent(event)
@@ -198,6 +313,51 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
self.__timer.start(100)
+ def startTimerTaskPolling(
+ self
+ ):
+
+ self.__timer_task_timer = QTimer()
+ self.__timer_task_timer.timeout.connect(self.pollTimerTaskQueue)
+ self.__timer_task_timer.start(500)
+
+
+ def pollTimerTaskQueue(
+ self
+ ):
+
+ if self.__is_running_timer_task:
+ return
+ try:
+ while not self.__is_running_timer_task:
+ timer_task = self.__timer_task_queue.get_nowait()
+ self.timerTaskIsRunning.emit(timer_task)
+ self.__timer_task_timer.stop()
+ self.__is_running_timer_task = True
+ self.setControlButtons(False, False, True)
+ if not timer_task["silent"]:
+ self.TrayIcon.showMessage(
+ "定时任务 - AutoLibrary",
+ f"\n已开始执行定时任务: \n{timer_task['name']}",
+ QSystemTrayIcon.MessageIcon.Information,
+ 1000
+ )
+ self.showNormal()
+ self.__current_timer_task_thread = TimerTaskWorker(
+ timer_task,
+ self.__input_queue,
+ self.__output_queue,
+ self.__config_paths
+ )
+ self.__current_timer_task_thread.finishedSignal_TimerWorker.connect(self.onTimerTaskFinished)
+ self.__current_timer_task_thread.showTraceSignal.connect(self.showTrace)
+ self.__current_timer_task_thread.showMsgSignal.connect(self.showMsg)
+ self.__current_timer_task_thread.start()
+ except queue.Empty:
+ self.__is_running_timer_task = False
+ pass
+
+
def setControlButtons(
self,
config_button_enabled: bool,
@@ -238,6 +398,15 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
except queue.Empty:
pass
+
+ @Slot()
+ def onTimerTaskWidgetClosed(
+ self
+ ):
+
+ self.TimerTaskWidgetButton.setEnabled(True)
+
+
@Slot(dict)
def onConfigWidgetClosed(
self,
@@ -253,6 +422,55 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
self.StopButton.setEnabled(False)
self.__config_paths = config_paths
+ @Slot(dict)
+ def onTimerTaskReady(
+ self,
+ timer_task: dict
+ ):
+
+ self.__timer_task_queue.put(timer_task)
+
+ @Slot(dict)
+ def onTimerTaskFinished(
+ self,
+ timer_task: dict
+ ):
+
+ self.__current_timer_task_thread.wait(1000)
+ self.__current_timer_task_thread.finishedSignal_TimerWorker.disconnect(self.onTimerTaskFinished)
+ self.__current_timer_task_thread.showTraceSignal.disconnect(self.showTrace)
+ self.__current_timer_task_thread.showMsgSignal.disconnect(self.showMsg)
+ self.__current_timer_task_thread.deleteLater()
+ self.__current_timer_task_thread = None
+ self.setControlButtons(True, True, False)
+ self.__is_running_timer_task = False
+ self.__timer_task_timer.start(500)
+ timer_task["executed"] = True
+ self.TrayIcon.showMessage(
+ "定时任务 - AutoLibrary",
+ f"\n定时任务 '{timer_task['name']}' 执行完成",
+ QSystemTrayIcon.MessageIcon.Information,
+ 1000
+ )
+ self.showTrace(f"定时任务 {timer_task['name']} 执行完成, uuid: {timer_task['task_uuid']}")
+ self.timerTaskIsExecuted.emit(timer_task)
+
+ @Slot()
+ def onTimerTaskWidgetButtonClicked(
+ self
+ ):
+ if self.__alTimerTaskWidget is None:
+ self.__alTimerTaskWidget = ALTimerTaskWidget(self)
+ self.timerTaskIsRunning.connect(self.__alTimerTaskWidget.onTimerTaskIsRunning)
+ self.timerTaskIsExecuted.connect(self.__alTimerTaskWidget.onTimerTaskIsExecuted)
+ self.__alTimerTaskWidget.timerTaskReady.connect(self.onTimerTaskReady)
+ self.__alTimerTaskWidget.timerTaskWidgetClosed.connect(self.onTimerTaskWidgetClosed)
+ self.__alTimerTaskWidget.setWindowFlags(Qt.Window)
+ self.__alTimerTaskWidget.show()
+ self.__alTimerTaskWidget.raise_()
+ self.__alTimerTaskWidget.activateWindow()
+ self.TimerTaskWidgetButton.setEnabled(False)
+
@Slot()
def onConfigButtonClicked(
self
@@ -281,7 +499,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
self.__auto_lib_thread = AutoLibWorker(
self.__input_queue,
self.__output_queue,
- self.__config_paths,
+ self.__config_paths
)
self.__auto_lib_thread.finishedSignal.connect(self.onStopButtonClicked)
self.__auto_lib_thread.showMsgSignal.connect(self.showMsg)
@@ -295,8 +513,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
if self.__auto_lib_thread:
self.showTrace("正在停止操作......")
- self.__auto_lib_thread.stop()
- self.__auto_lib_thread.wait()
+ self.__auto_lib_thread.wait(2000)
self.showTrace("操作已停止")
self.__auto_lib_thread.showMsgSignal.disconnect(self.showMsg)
self.__auto_lib_thread.showTraceSignal.disconnect(self.showTrace)
diff --git a/src/gui/ALMainWindow.ui b/src/gui/ALMainWindow.ui
index 7a43677..c2916f4 100644
--- a/src/gui/ALMainWindow.ui
+++ b/src/gui/ALMainWindow.ui
@@ -50,11 +50,33 @@
5
+
-
+
+
+
+ 25
+ 25
+
+
+
+
+ 25
+ 25
+
+
+
+
+
+
+
+
+
+
-
- 1280
+ 1000
0
@@ -237,6 +259,9 @@ font: 700 9pt "Microsoft YaHei UI";
发送
+
+
+
@@ -245,7 +270,7 @@ font: 700 9pt "Microsoft YaHei UI";
diff --git a/src/gui/ALTimerTaskWidget.py b/src/gui/ALTimerTaskWidget.py
new file mode 100644
index 0000000..3920e37
--- /dev/null
+++ b/src/gui/ALTimerTaskWidget.py
@@ -0,0 +1,313 @@
+# -*- coding: utf-8 -*-
+"""
+Copyright (c) 2025 KenanZhu.
+All rights reserved.
+
+This software is provided "as is", without any warranty of any kind.
+You may use, modify, and distribute this file under the terms of the MIT License.
+See the LICENSE file for details.
+"""
+import os
+import sys
+import time
+import queue
+
+from enum import Enum
+from datetime import datetime, timedelta
+
+from PySide6.QtCore import (
+ Qt, Signal, Slot, QTimer
+)
+from PySide6.QtWidgets import (
+ QDialog, QWidget, QListWidgetItem, QMessageBox,
+ QHBoxLayout, QVBoxLayout, QLabel, QPushButton
+)
+from PySide6.QtGui import (
+ QCloseEvent
+)
+
+from gui.Ui_ALTimerTaskWidget import Ui_ALTimerTaskWidget
+from gui.ALAddTimerTaskDialog import ALAddTimerTaskWidget, TimerTaskStatus
+
+
+class TimerTaskItemWidget(QWidget):
+
+ def __init__(
+ self,
+ parent = None,
+ timer_task: dict = None
+ ):
+
+ super().__init__(parent)
+
+ self.__timer_task = timer_task
+ self.modifyUi()
+
+
+ def modifyUi(
+ self
+ ):
+
+ self.ItemWidgetLayout = QHBoxLayout(self)
+ self.ItemWidgetLayout.setSpacing(10)
+ self.ItemWidgetLayout.setContentsMargins(10, 5, 10, 5)
+
+ self.TaskInfoLayout = QVBoxLayout()
+ self.TaskInfoLayout.setSpacing(5)
+ TaskNameLabel = QLabel(self.__timer_task["name"])
+ TaskNameLabelFont = TaskNameLabel.font()
+ TaskNameLabelFont.setBold(True)
+ TaskNameLabel.setFont(TaskNameLabelFont)
+ TaskNameLabel.setFixedHeight(25)
+ self.TaskInfoLayout.addWidget(TaskNameLabel)
+
+ ExecuteTimeStr = self.__timer_task["execute_time"].strftime("%Y-%m-%d %H:%M:%S")
+ ExecuteTimeLabel = QLabel(f"执行时间: {ExecuteTimeStr}")
+ ExecuteTimeLabel.setStyleSheet("color: gray;")
+ ExecuteTimeLabel.setFixedHeight(20)
+ self.TaskInfoLayout.addWidget(ExecuteTimeLabel)
+
+ self.ItemWidgetLayout.addLayout(self.TaskInfoLayout)
+ self.ItemWidgetLayout.addStretch()
+
+ match self.__timer_task["status"]:
+ case TimerTaskStatus.PENDING:
+ TaskStatusText = "等待中"
+ TaskStatusColor = "#FF9800"
+ case TimerTaskStatus.READY:
+ TaskStatusText = "已就绪"
+ TaskStatusColor = "#316BFF"
+ case TimerTaskStatus.RUNNING:
+ TaskStatusText = "执行中"
+ TaskStatusColor = "#2294FF"
+ case TimerTaskStatus.EXECUTED:
+ TaskStatusText = "已执行"
+ TaskStatusColor = "#4CAF50"
+ case TimerTaskStatus.OUTDATED:
+ TaskStatusText = "已过期"
+ TaskStatusColor = "#FF5722"
+ TaskStatusLabel = QLabel(TaskStatusText)
+ TaskStatusLabel.setStyleSheet(f"""
+ QLabel {{
+ background-color: {TaskStatusColor};
+ color: white;
+ border-radius: 5px;
+ font-weight: bold;
+ }}
+ """)
+ TaskStatusLabel.setAlignment(Qt.AlignmentFlag.AlignCenter)
+ TaskStatusLabel.setFixedSize(80, 25)
+ self.ItemWidgetLayout.addWidget(TaskStatusLabel)
+
+ TaskModeText = "静默" if self.__timer_task["silent"] else "显示"
+ TaskModeColor = "#6325FF" if self.__timer_task["silent"] else "#2294FF"
+ TaskModeLabel = QLabel(TaskModeText)
+ TaskModeLabel.setStyleSheet(f"""
+ QLabel {{
+ background-color: {TaskModeColor};
+ color: white;
+ border-radius: 5px;
+ font-weight: bold;
+ }}
+ """)
+ TaskModeLabel.setAlignment(Qt.AlignmentFlag.AlignCenter)
+ TaskModeLabel.setFixedSize(60, 25)
+ self.ItemWidgetLayout.addWidget(TaskModeLabel)
+
+ self.DeleteButton = QPushButton("删除")
+ self.DeleteButton.setFixedSize(80, 25)
+ self.ItemWidgetLayout.addWidget(self.DeleteButton)
+ if self.__timer_task["status"] == TimerTaskStatus.READY\
+ or self.__timer_task["status"] == TimerTaskStatus.RUNNING:
+ self.DeleteButton.setEnabled(False)
+ self.setFixedHeight(55)
+
+
+class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
+
+ timerTasksChanged = Signal(list)
+ timerTaskReady = Signal(dict)
+ timerTaskWidgetClosed = Signal()
+
+ def __init__(
+ self,
+ parent = None
+ ):
+
+ super().__init__(parent)
+
+ self.__timer_tasks = []
+ self.__check_timer = None
+ self.setupUi(self)
+ self.connectSignals()
+ self.setupTimer()
+
+
+ def setupTimer(
+ self
+ ):
+
+ self.__check_timer = QTimer(self)
+ self.__check_timer.timeout.connect(self.checkTasks)
+ self.__check_timer.start(500)
+
+
+ def connectSignals(
+ self
+ ):
+
+ self.AddTimerTaskButton.clicked.connect(self.addTask)
+ self.ClearAllTimerTasksButton.clicked.connect(self.clearAllTasks)
+
+
+ def closeEvent(
+ self,
+ event: QCloseEvent
+ ):
+
+ self.hide()
+ self.timerTaskWidgetClosed.emit()
+ event.ignore()
+
+
+ def updateStat(
+ self
+ ):
+
+ pending = 0
+ in_queue = 0
+ executed = 0
+ total = len(self.__timer_tasks)
+ for timer_task in self.__timer_tasks:
+ if timer_task["status"] == TimerTaskStatus.PENDING:
+ pending += 1
+ elif timer_task["status"] == TimerTaskStatus.READY\
+ or timer_task["status"] == TimerTaskStatus.RUNNING:
+ in_queue += 1
+ elif timer_task["status"] == TimerTaskStatus.EXECUTED:
+ executed += 1
+ self.TotalTaskLabel.setText(f"总任务:{total}")
+ self.PendingTaskLabel.setText(f"待执行:{pending}")
+ self.InQueueTaskLabel.setText(f"队列中:{in_queue}")
+ self.ExecutedTaskLabel.setText(f"已执行:{executed}")
+
+
+ def updateTimerTaskList(
+ self
+ ):
+
+ self.TimerTasksListWidget.clear()
+ self.__timer_tasks.sort(
+ key = lambda x: x["execute_time"]
+ )
+ for timer_task in self.__timer_tasks:
+ item = QListWidgetItem()
+ item.setData(Qt.UserRole, timer_task)
+ widget = TimerTaskItemWidget(self, timer_task)
+ widget.DeleteButton.clicked.connect(
+ lambda _, uuid = timer_task["task_uuid"]: self.deleteTask(uuid)
+ )
+ item.setSizeHint(widget.size())
+ self.TimerTasksListWidget.addItem(item)
+ self.TimerTasksListWidget.setItemWidget(item, widget)
+
+
+ def addTask(
+ self
+ ):
+
+ dialog = ALAddTimerTaskWidget(self)
+ if dialog.exec() == QDialog.DialogCode.Accepted:
+ timer_task = dialog.getTimerTask()
+ self.__timer_tasks.append(timer_task)
+ self.updateTimerTaskList()
+ self.updateStat()
+
+
+ def deleteTask(
+ self,
+ task_uuid: str
+ ):
+
+ self.__timer_tasks = [
+ x for x in self.__timer_tasks
+ if x["task_uuid"] != task_uuid
+ ]
+ self.updateTimerTaskList()
+ self.updateStat()
+
+
+ def clearAllTasks(
+ self
+ ):
+
+ if not self.__timer_tasks:
+ return
+ result = QMessageBox.question(
+ self,
+ "确认 - AutoLibrary",
+ "是否要清除所有定时任务 ?",
+ QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
+ )
+ if result is QMessageBox.StandardButton.No:
+ return
+ in_queue_tasks = [
+ x for x in self.__timer_tasks
+ if x["status"] == TimerTaskStatus.READY
+ or x["status"] == TimerTaskStatus.RUNNING
+ ]
+ in_queue_count = len(in_queue_tasks)
+ if in_queue_count > 0:
+ QMessageBox.warning(
+ self,
+ "警告 - AutoLibrary",
+ "存在正在执行或已就绪的队列任务,无法清除所有定时任务 !"
+ )
+ self.__timer_tasks = in_queue_tasks
+ self.updateTimerTaskList()
+ self.updateStat()
+
+
+ def checkTasks(
+ self
+ ):
+
+ now = datetime.now()
+ for timer_task in self.__timer_tasks:
+ if timer_task["execute_time"] > now:
+ continue
+ if timer_task["status"] is not TimerTaskStatus.PENDING:
+ continue
+ if timer_task["execute_time"] <= now + timedelta(seconds = -5):
+ timer_task["status"] = TimerTaskStatus.OUTDATED
+ else:
+ timer_task["status"] = TimerTaskStatus.READY
+ self.timerTaskReady.emit(timer_task)
+ self.updateTimerTaskList()
+ self.updateStat()
+
+
+ @Slot(dict)
+ def onTimerTaskIsRunning(
+ self,
+ timer_task: dict
+ ):
+
+ for task in self.__timer_tasks:
+ if task["task_uuid"] == timer_task["task_uuid"]:
+ task["status"] = TimerTaskStatus.RUNNING
+ self.updateTimerTaskList()
+ self.updateStat()
+
+
+ @Slot(dict)
+ def onTimerTaskIsExecuted(
+ self,
+ timer_task: dict
+ ):
+
+ for task in self.__timer_tasks:
+ if task["task_uuid"] == timer_task["task_uuid"]:
+ task["status"] = TimerTaskStatus.EXECUTED
+ self.updateTimerTaskList()
+ self.updateStat()
diff --git a/src/gui/ALTimerTaskWidget.ui b/src/gui/ALTimerTaskWidget.ui
new file mode 100644
index 0000000..fcfa810
--- /dev/null
+++ b/src/gui/ALTimerTaskWidget.ui
@@ -0,0 +1,239 @@
+
+
+ ALTimerTaskWidget
+
+
+
+ 0
+ 0
+ 400
+ 400
+
+
+
+
+ 400
+ 400
+
+
+
+
+ 600
+ 400
+
+
+
+ 定时任务 - AutoLibrary
+
+
+
+ 5
+
+
+ 5
+
+
+ 5
+
+
+ 5
+
+
+ 5
+
+ -
+
+
+ 5
+
+
-
+
+
+
+ 70
+ 25
+
+
+
+
+ 70
+ 25
+
+
+
+ 总任务:0
+
+
+
+ -
+
+
+
+ 70
+ 25
+
+
+
+
+ 70
+ 25
+
+
+
+ QLabel {
+ color: #FF9800
+}
+
+
+ 待执行:0
+
+
+
+ -
+
+
+
+ 70
+ 25
+
+
+
+
+ 70
+ 25
+
+
+
+ QLabel {
+ color: #2294FF
+}
+
+
+ 队列中:0
+
+
+
+ -
+
+
+
+ 70
+ 25
+
+
+
+
+ 70
+ 25
+
+
+
+ QLabel {
+ color: #4CAF50
+}
+
+
+ 已执行:0
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 600
+ 16777215
+
+
+
+ QFrame::Shape::NoFrame
+
+
+ QFrame::Shadow::Plain
+
+
+
+
+
+ -
+
+
+ true
+
+
+
+ -
+
+
+ 5
+
+
-
+
+
+
+ 80
+ 25
+
+
+
+
+ 80
+ 25
+
+
+
+ 清除全部
+
+
+
+ -
+
+
+
+ 80
+ 25
+
+
+
+
+ 80
+ 25
+
+
+
+ 添加任务
+
+
+
+ -
+
+
+
+ 16777215
+ 16777215
+
+
+
+ QFrame::Shape::NoFrame
+
+
+ QFrame::Shadow::Plain
+
+
+ 0
+
+
+
+
+
+
+
+
+
+
diff --git a/src/gui/AppInfo.py b/src/gui/AppInfo.py
new file mode 100644
index 0000000..2ffa437
--- /dev/null
+++ b/src/gui/AppInfo.py
@@ -0,0 +1 @@
+AL_VERSION = "1.0.0-beta"