1
1
mirror of https://github.com/KenanZhu/AutoLibrary.git synced 2026-06-17 23:13:03 +08:00

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...
This commit is contained in:
2025-11-29 20:03:45 +08:00
parent c5e589f3d1
commit 0a8763add5
11 changed files with 1513 additions and 16 deletions
+4
View File
@@ -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
+141
View File
@@ -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"""
<h4>Version Information:</h4>
Version: {AL_VERSION}<br>
Python version: {platform.python_version()}<br>
Qt version: {self.getQtVersion()}<br>
<h4>Author Information:</h4>
Developer: KenanZhu<br>
Contact: nanoki_zh@163.com<br>
GitHub: <a href="https://www.github.com/KenanZhu" style="text-decoration: none;">https://www.github.com/KenanZhu</a><br>
<h4>Project Information:</h4>
License: MIT License<br>
Project repository: <a href="https://www.github.com/KenanZhu/AutoLibrary" style="text-decoration: none;">https://www.github.com/KenanZhu/AutoLibrary</a><br>
Project website: <a href="https://www.autolibrary.cv/" style="text-decoration: none;">https://www.autolibrary.cv/</a><br>
<h4>System Information:</h4>
Processor: {platform.processor()}<br>
Operating system: {os_info['system']}<br>
System version: {os_info['version']}<br>
System architecture: {os_info['architecture']}<br>
"""
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))
+140
View File
@@ -0,0 +1,140 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ALAboutDialog</class>
<widget class="QDialog" name="ALAboutDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>300</width>
<height>300</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>300</width>
<height>300</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>800</width>
<height>300</height>
</size>
</property>
<property name="windowTitle">
<string>关于 - AutoLibrary</string>
</property>
<layout class="QVBoxLayout" name="ALAboutDialogLayout">
<property name="spacing">
<number>5</number>
</property>
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<number>5</number>
</property>
<item>
<layout class="QHBoxLayout" name="LogoLayout">
<property name="spacing">
<number>5</number>
</property>
<item>
<widget class="QLabel" name="LogoIconLabel">
<property name="minimumSize">
<size>
<width>56</width>
<height>56</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>56</width>
<height>56</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
<property name="indent">
<number>0</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="LogoTextLabel">
<property name="font">
<font>
<pointsize>24</pointsize>
<bold>true</bold>
</font>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<property name="text">
<string>AutoLibrary</string>
</property>
<property name="margin">
<number>0</number>
</property>
<property name="indent">
<number>0</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QTextEdit" name="AboutInfoEdit">
<property name="font">
<font>
<family>Courier New</family>
<bold>false</bold>
</font>
</property>
<property name="lineWrapMode">
<enum>QTextEdit::LineWrapMode::NoWrap</enum>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::TextInteractionFlag::TextBrowserInteraction</set>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="CopyButton">
<property name="minimumSize">
<size>
<width>80</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>80</width>
<height>25</height>
</size>
</property>
<property name="text">
<string>复制</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
+148
View File
@@ -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)
+249
View File
@@ -0,0 +1,249 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ALAddTimerTaskDialog</class>
<widget class="QDialog" name="ALAddTimerTaskDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>300</width>
<height>300</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>300</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>500</width>
<height>300</height>
</size>
</property>
<property name="windowTitle">
<string>添加定时任务 - AutoLibrary</string>
</property>
<property name="sizeGripEnabled">
<bool>true</bool>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="ALAddTimerTaskLayout">
<property name="spacing">
<number>5</number>
</property>
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<number>5</number>
</property>
<item>
<layout class="QHBoxLayout" name="TaskNameLayout">
<property name="spacing">
<number>5</number>
</property>
<item>
<widget class="QLabel" name="TaskNameLabel">
<property name="minimumSize">
<size>
<width>60</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>25</height>
</size>
</property>
<property name="text">
<string>任务名称:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="TaskNameLineEdit"/>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="TimerConfigGroupBox">
<property name="title">
<string>定时设置</string>
</property>
<layout class="QVBoxLayout" name="TimerConfigLayout">
<property name="spacing">
<number>5</number>
</property>
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<number>5</number>
</property>
<item>
<layout class="QHBoxLayout" name="TimerTypeSelectLayout">
<property name="spacing">
<number>5</number>
</property>
<item>
<widget class="QLabel" name="TimerTypeLabel">
<property name="text">
<string>定时类型:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="TimerTypeComboBox">
<item>
<property name="text">
<string>特定时间</string>
</property>
</item>
<item>
<property name="text">
<string>相对时间</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="TaskConfigGroupBox">
<property name="title">
<string>运行设置</string>
</property>
<layout class="QGridLayout" name="TaskConfigLayout">
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<number>5</number>
</property>
<property name="spacing">
<number>5</number>
</property>
<item row="0" column="0">
<widget class="QRadioButton" name="SilentlyRunRadioButton">
<property name="text">
<string>静默运行</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="autoRepeat">
<bool>false</bool>
</property>
<property name="autoExclusive">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QRadioButton" name="ShowBeforeRunRadioButton">
<property name="text">
<string>运行前提示</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="ControLayout">
<property name="spacing">
<number>5</number>
</property>
<item>
<widget class="QFrame" name="ControlSpaceFrame">
<property name="frameShape">
<enum>QFrame::Shape::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Plain</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="CancelButton">
<property name="minimumSize">
<size>
<width>80</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>80</width>
<height>25</height>
</size>
</property>
<property name="text">
<string>取消</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="ConfirmButton">
<property name="minimumSize">
<size>
<width>80</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>80</width>
<height>25</height>
</size>
</property>
<property name="text">
<string>添加</string>
</property>
<property name="autoDefault">
<bool>true</bool>
</property>
<property name="default">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
+9
View File
@@ -1829,6 +1829,9 @@
<property name="text">
<string>导出配置文件</string>
</property>
<property name="icon">
<iconset theme="document-save"/>
</property>
</widget>
</item>
</layout>
@@ -1924,6 +1927,9 @@
<property name="text">
<string>新建配置</string>
</property>
<property name="icon">
<iconset theme="document-new"/>
</property>
</widget>
</item>
<item>
@@ -1943,6 +1949,9 @@
<property name="text">
<string>加载配置</string>
</property>
<property name="icon">
<iconset theme="document-open"/>
</property>
</widget>
</item>
<item>
+231 -14
View File
@@ -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)
+38 -2
View File
@@ -50,11 +50,33 @@
<property name="spacing">
<number>5</number>
</property>
<item>
<widget class="QPushButton" name="TimerTaskWidgetButton">
<property name="minimumSize">
<size>
<width>25</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>25</width>
<height>25</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="document-open-recent"/>
</property>
</widget>
</item>
<item>
<widget class="QFrame" name="ControlSpaceFrame">
<property name="minimumSize">
<size>
<width>1280</width>
<width>1000</width>
<height>0</height>
</size>
</property>
@@ -237,6 +259,9 @@ font: 700 9pt &quot;Microsoft YaHei UI&quot;;</string>
<property name="text">
<string>发送</string>
</property>
<property name="icon">
<iconset theme="document-send"/>
</property>
</widget>
</item>
</layout>
@@ -245,7 +270,7 @@ font: 700 9pt &quot;Microsoft YaHei UI&quot;;</string>
</widget>
<widget class="QMenuBar" name="MenuBar">
<property name="enabled">
<bool>false</bool>
<bool>true</bool>
</property>
<property name="geometry">
<rect>
@@ -258,6 +283,17 @@ font: 700 9pt &quot;Microsoft YaHei UI&quot;;</string>
<property name="nativeMenuBar">
<bool>true</bool>
</property>
<widget class="QMenu" name="HelpMenu">
<property name="mouseTracking">
<bool>true</bool>
</property>
<property name="title">
<string>帮助</string>
</property>
<addaction name="ManualAction"/>
<addaction name="AboutAction"/>
</widget>
<addaction name="HelpMenu"/>
</widget>
<widget class="QStatusBar" name="StatusBar">
<property name="enabled">
+313
View File
@@ -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()
+239
View File
@@ -0,0 +1,239 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ALTimerTaskWidget</class>
<widget class="QWidget" name="ALTimerTaskWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>400</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>400</width>
<height>400</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>600</width>
<height>400</height>
</size>
</property>
<property name="windowTitle">
<string>定时任务 - AutoLibrary</string>
</property>
<layout class="QVBoxLayout" name="ALTimerTaskWidgetLayout">
<property name="spacing">
<number>5</number>
</property>
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<number>5</number>
</property>
<item>
<layout class="QHBoxLayout" name="TimerTaskStatusLayout">
<property name="spacing">
<number>5</number>
</property>
<item>
<widget class="QLabel" name="TotalTaskLabel">
<property name="minimumSize">
<size>
<width>70</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>70</width>
<height>25</height>
</size>
</property>
<property name="text">
<string>总任务:0</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="PendingTaskLabel">
<property name="minimumSize">
<size>
<width>70</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>70</width>
<height>25</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">QLabel {
color: #FF9800
}</string>
</property>
<property name="text">
<string>待执行:0</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="InQueueTaskLabel">
<property name="minimumSize">
<size>
<width>70</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>70</width>
<height>25</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">QLabel {
color: #2294FF
}</string>
</property>
<property name="text">
<string>队列中:0</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="ExecutedTaskLabel">
<property name="minimumSize">
<size>
<width>70</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>70</width>
<height>25</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">QLabel {
color: #4CAF50
}</string>
</property>
<property name="text">
<string>已执行:0</string>
</property>
</widget>
</item>
<item>
<widget class="QFrame" name="TimerTaskSpaceFrame">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>600</width>
<height>16777215</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::Shape::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Plain</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QListWidget" name="TimerTasksListWidget">
<property name="enabled">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="TimerTaskEditLayout">
<property name="spacing">
<number>5</number>
</property>
<item>
<widget class="QPushButton" name="ClearAllTimerTasksButton">
<property name="minimumSize">
<size>
<width>80</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>80</width>
<height>25</height>
</size>
</property>
<property name="text">
<string>清除全部</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="AddTimerTaskButton">
<property name="minimumSize">
<size>
<width>80</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>80</width>
<height>25</height>
</size>
</property>
<property name="text">
<string>添加任务</string>
</property>
</widget>
</item>
<item>
<widget class="QFrame" name="TimerTaskEditSpaceFrame">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::Shape::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Plain</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
+1
View File
@@ -0,0 +1 @@
AL_VERSION = "1.0.0-beta"