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"