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

Compare commits

...

7 Commits

12 changed files with 246 additions and 275 deletions
+11 -4
View File
@@ -33,7 +33,7 @@ from .ASTokenizer import (
)
__all__ = ["execute", "addTargetVar"]
__all__ = ["execute", "addTargetVar", "splitTopLevel"]
# Engine state
@@ -69,7 +69,7 @@ _RE_CUR_DATE_OFFSET = re.compile(r"^CURRENT_DATE\s*\+\s*(\d+)$", re.IGNORECASE)
_RE_CUR_TIME_OFFSET = re.compile(r"^CURRENT_TIME\s*\+\s*(\d+)$", re.IGNORECASE)
def _splitTopLevel(
def splitTopLevel(
text: str,
delimiter: str
) -> list:
@@ -281,13 +281,13 @@ def _evaluateCondition(
s = condition_str.strip()
if not s:
return False
or_parts = _splitTopLevel(s, ".OR.")
or_parts = splitTopLevel(s, ".OR.")
if len(or_parts) > 1:
return any(
_evaluateCondition(p.strip(), target_data, line)
for p in or_parts
)
and_parts = _splitTopLevel(s, ".AND.")
and_parts = splitTopLevel(s, ".AND.")
if len(and_parts) > 1:
return all(
_evaluateCondition(p.strip(), target_data, line)
@@ -466,12 +466,14 @@ class _EngineExecutor(NodeVisitor):
return self._cur_line
def _incLine(
self
):
self._cur_line += 1
def visitScript(
self,
_node: Script
@@ -480,6 +482,7 @@ class _EngineExecutor(NodeVisitor):
for child in _node.body:
child.accept(self)
def visitIf(
self,
_node: IfNode
@@ -506,6 +509,7 @@ class _EngineExecutor(NodeVisitor):
for child in _node.else_body:
child.accept(self)
def visitSet(
self,
_node: SetNode
@@ -515,6 +519,7 @@ class _EngineExecutor(NodeVisitor):
full_line = f"SET {_node.target} = {_node.value}"
_executeSet(full_line, self._target_data, self._line)
def visitOp(
self,
_node: OpNode
@@ -525,6 +530,7 @@ class _EngineExecutor(NodeVisitor):
full_line = f"{_node.target} .{op_upper}. {_node.value}"
_executeOperation(full_line, self._target_data, self._line)
def visitPass(
self,
_node: PassNode
@@ -532,6 +538,7 @@ class _EngineExecutor(NodeVisitor):
self._incLine()
def visitUnrecog(
self,
_node: UnrecogNode
+16 -33
View File
@@ -17,7 +17,7 @@ from datetime import (
from .ASObject import ASObject
__all__ = ["ASOperator"]
__all__ = ["ASOperator", "ARITH_TYPES", "COMPARISON_OPERATORS"]
class ASOperator:
@@ -96,12 +96,11 @@ class ASOperator:
target_val = target.getValue(target_data)
if target_val is None:
raise ValueError(f"'{target.name}' 的值为空,无法进行运算")
if op == ".ADD.":
cls._arithAdd(target, target_val, operand, target_data)
elif op == ".SUB.":
cls._arithSub(target, target_val, operand, target_data)
else:
op_name = "ADD" if op == ".ADD." else "SUB" if op == ".SUB." else None
if op_name is None:
raise ValueError(f"不支持的操作 '{op}'")
sign = 1 if op == ".ADD." else -1
cls._arithBinary(target, target_val, operand, target_data, sign, op_name)
@classmethod
def _arithBinary(
@@ -110,13 +109,13 @@ class ASOperator:
target_val,
operand: ASObject,
target_data: dict,
sign: int
sign: int,
op_name: str = ""
):
"""Apply ADD (sign=1) or SUB (sign=-1) per type."""
"""Apply arithmetic per type."""
tp = target.var_type
raw_op = operand._value
op_name = "ADD" if sign == 1 else "SUB"
if tp == "Date":
if not isinstance(target_val, date):
@@ -129,35 +128,13 @@ class ASOperator:
dt = datetime.combine(datetime.today(), target_val) + delta
new_val = dt.time()
elif tp == "Int":
new_val = int(target_val) + int(raw_op) * sign
new_val = int(target_val) + int(raw_op)*sign
elif tp == "Float":
new_val = float(target_val) + float(raw_op) * sign
new_val = float(target_val) + float(raw_op)*sign
else:
raise ValueError(f"'{tp}' 类型不支持 {op_name} 操作")
target.setValue(new_val, target_data)
@classmethod
def _arithAdd(
cls,
target: ASObject,
target_val,
operand: ASObject,
target_data: dict
):
"""Dispatch ADD per type."""
cls._arithBinary(target, target_val, operand, target_data, 1)
@classmethod
def _arithSub(
cls,
target: ASObject,
target_val,
operand: ASObject,
target_data: dict
):
"""Dispatch SUB per type."""
cls._arithBinary(target, target_val, operand, target_data, -1)
@classmethod
def compare(
cls,
@@ -206,3 +183,9 @@ class ASOperator:
f"无法比较 '{left.name}' ({left.var_type}) "
f"'{right.name}' ({right.var_type})"
)
# Public constants
# may be used by the GUI orchestration dialog.
ARITH_TYPES = ASOperator._ARITH_TYPES
COMPARISON_OPERATORS = set(ASOperator._COMPARE.keys())
+23 -9
View File
@@ -381,6 +381,20 @@ class ASTokenizer:
stmt.op_type = OP_SUB
return stmt
@classmethod
def _stripComment(
cls,
line: str
) -> str:
in_single = False
for i, ch in enumerate(line):
if ch == "'":
in_single = not in_single
elif ch == "/" and i + 1 < len(line) and line[i + 1] == "/" and not in_single:
return line[:i].rstrip()
return line
@classmethod
def _tokenizeImpl(
cls,
@@ -389,11 +403,11 @@ class ASTokenizer:
statements = []
for raw_line in script.split("\n"):
stripped = raw_line.strip()
if not stripped:
code = cls._stripComment(raw_line.strip())
if not code:
continue
kind, data = cls._matchLine(stripped)
statements.append(cls._buildStmt(stripped, kind, data))
kind, data = cls._matchLine(code)
statements.append(cls._buildStmt(code, kind, data))
return statements
@classmethod
@@ -476,12 +490,12 @@ class ASTokenizer:
cls._notifyObservers(observers, "onParseStart", script)
statements = []
for i, raw_line in enumerate(script.split("\n"), 1):
stripped = raw_line.strip()
if not stripped:
code = cls._stripComment(raw_line.strip())
if not code:
continue
kind, data = cls._matchLine(stripped)
cls._notifyObservers(observers, "onTokenParsed", kind, data, i, stripped)
statements.append(cls._buildStmt(stripped, kind, data))
kind, data = cls._matchLine(code)
cls._notifyObservers(observers, "onTokenParsed", kind, data, i, code)
statements.append(cls._buildStmt(code, kind, data))
cls._notifyObservers(observers, "onParseComplete", statements)
return statements
+12 -12
View File
@@ -14,13 +14,16 @@ from autoscript.ASTokenizer import (
from autoscript.ASEngine import (
execute,
addTargetVar,
splitTopLevel,
)
from autoscript.ASObject import _META_VARS as META_VARS
from autoscript.ASObserver import ParsingObserver
__all__ = [
"execute",
"addTargetVar",
"splitTopLevel",
"registerDefaultTargetVars",
"buildMockTargetData",
"META_VARS",
@@ -35,18 +38,6 @@ __all__ = [
"ParsingObserver",
]
# All variables available to scripts (display_name -> (name, type)).
# This mirrors the old AutoScriptEngine.VARIABLE_META for backward
# compatibility in the UI orchestration dialog.
ALL_VARIABLES: dict = {
"用户名": ("USERNAME", "String"),
"用户启用": ("USER_ENABLE", "Boolean"),
"预约日期": ("RESERVE_DATE", "Date"),
"预约开始时间": ("RESERVE_BEGIN_TIME", "Time"),
"预约结束时间": ("RESERVE_END_TIME", "Time"),
"当前时间": ("CURRENT_TIME", "Time"),
"当前日期": ("CURRENT_DATE", "Date"),
}
# Key paths into target_data dict for each target variable.
# (name, type, key_path, display_name)
@@ -57,6 +48,15 @@ _TARGET_VAR_DEFS = [
("RESERVE_BEGIN_TIME", "Time", ["reserve_info", "begin_time", "time"], "预约开始时间"),
("RESERVE_END_TIME", "Time", ["reserve_info", "end_time", "time"], "预约结束时间"),
]
# All variables (display_name -> (name, type)), derived from target vars + meta vars.
ALL_VARIABLES = {
display_name: (name, var_type)
for name, var_type, _, display_name in _TARGET_VAR_DEFS
} | {
obj.display_name: (obj.name, obj.var_type)
for obj in META_VARS.values()
}
_MOCK_TYPE_VALUES = {
"String": "__mock__",
"Boolean": True,
+62 -70
View File
@@ -44,44 +44,44 @@ class ALScriptHighlighter(QSyntaxHighlighter):
self._rules = []
keywordFmt = QTextCharFormat()
keywordFmt.setForeground(QColor("#007ACC"))
keywordFmt.setForeground(QColor("#569CD6"))
keywordFmt.setFontWeight(QFont.Weight.Bold)
for kw in ["IF", "ELSE IF", "ELSE", "ENDIF", "END IF",
"SET", "PASS", "THEN"]:
pattern = r"\b" + kw.replace(" ", r"\s+") + r"\b"
self._rules.append((pattern, keywordFmt))
opFmt = QTextCharFormat()
opFmt.setForeground(QColor("#AF00DB"))
opFmt.setForeground(QColor("#C586C0"))
opFmt.setFontWeight(QFont.Weight.Normal)
for op in [r"\.EQ\.", r"\.NEQ\.", r"\.BGT\.", r"\.BLT\.",
r"\.BGE\.", r"\.BLE\.", r"\.ADD\.", r"\.SUB\.",
r"\.AND\.", r"\.OR\."]:
self._rules.append((op, opFmt))
literalFmt = QTextCharFormat()
literalFmt.setForeground(QColor("#AF00DB"))
literalFmt.setFontWeight(QFont.Weight.Bold)
for lit in [".TRUE.", ".FALSE."]:
self._rules.append((r"\b" + lit.replace(".", r"\.") + r"\b", literalFmt))
boolFmt = QTextCharFormat()
boolFmt.setForeground(QColor("#4FC1FF"))
boolFmt.setFontWeight(QFont.Weight.Bold)
self._rules.append((r"\.TRUE\.", boolFmt))
self._rules.append((r"\.FALSE\.", boolFmt))
funcFmt = QTextCharFormat()
funcFmt.setForeground(QColor("#795E26"))
funcFmt.setForeground(QColor("#DCDCAA"))
funcFmt.setFontWeight(QFont.Weight.Normal)
self._rules.append((r"\b(?:DATE|TIME)\b", funcFmt))
varFmt = QTextCharFormat()
varFmt.setForeground(QColor("#267F99"))
varFmt.setForeground(QColor("#9CDCFE"))
varFmt.setFontWeight(QFont.Weight.Normal)
var_names = [name for _, (name, _) in ALL_VARIABLES.items()]
for var in var_names:
self._rules.append((r"\b" + var + r"\b", varFmt))
strFmt = QTextCharFormat()
strFmt.setForeground(QColor("#A31515"))
strFmt.setForeground(QColor("#CE9178"))
strFmt.setFontWeight(QFont.Weight.Normal)
self._rules.append((r"'[^']*'", strFmt))
numFmt = QTextCharFormat()
numFmt.setForeground(QColor("#098658"))
numFmt.setForeground(QColor("#B5CEA8"))
numFmt.setFontWeight(QFont.Weight.Normal)
self._rules.append((r"\b\d+\b", numFmt))
commentFmt = QTextCharFormat()
commentFmt.setForeground(QColor("#008000"))
commentFmt.setForeground(QColor("#6A9955"))
commentFmt.setFontItalic(True)
self._rules.append((r"//[^\n]*", commentFmt))
@@ -110,16 +110,15 @@ class ALAutoScriptEditDialog(QDialog):
super().__init__(parent)
self._fontSize = 19
self.modifyUi()
self.setupUi()
self.connectSignals()
self._textEdit.setPlainText(script)
self.textEdit.setPlainText(script)
self._highlighter = ALScriptHighlighter(
self._textEdit.document()
self.textEdit.document()
)
def modifyUi(
def setupUi(
self
):
@@ -129,58 +128,53 @@ class ALAutoScriptEditDialog(QDialog):
layout.setSpacing(4)
layout.setContentsMargins(4, 4, 4, 4)
toolbarLayout = QHBoxLayout()
self._zoomInBtn = QPushButton("")
self._zoomInBtn.setFixedSize(25, 25)
self._zoomOutBtn = QPushButton("")
self._zoomOutBtn.setFixedSize(25, 25)
self._zoomResetBtn = QPushButton(
self.zoomInBtn = QPushButton("")
self.zoomInBtn.setFixedSize(25, 25)
self.zoomOutBtn = QPushButton("")
self.zoomOutBtn.setFixedSize(25, 25)
self.zoomResetBtn = QPushButton(
QApplication.style().standardIcon(
QStyle.StandardPixmap.SP_BrowserReload
), ""
)
self._zoomResetBtn.setFixedSize(25, 25)
self._zoomResetBtn.setToolTip("重置缩放")
self._zoomLabel = QLabel(f"{self._fontSize}px")
self._zoomLabel.setFixedHeight(25)
toolbarLayout.addWidget(self._zoomInBtn)
toolbarLayout.addWidget(self._zoomOutBtn)
toolbarLayout.addWidget(self._zoomResetBtn)
toolbarLayout.addWidget(self._zoomLabel)
self.zoomResetBtn.setFixedSize(25, 25)
self.zoomResetBtn.setToolTip("重置缩放")
self.zoomLabel = QLabel(f"{self._fontSize}px")
self.zoomLabel.setFixedHeight(25)
toolbarLayout.addWidget(self.zoomInBtn)
toolbarLayout.addWidget(self.zoomOutBtn)
toolbarLayout.addWidget(self.zoomResetBtn)
toolbarLayout.addWidget(self.zoomLabel)
toolbarLayout.addStretch()
self._copyBtn = QPushButton(
self.copyBtn = QPushButton(
QApplication.style().standardIcon(
QStyle.StandardPixmap.SP_FileDialogDetailedView
), ""
)
self._copyBtn.setFixedSize(25, 25)
self._copyBtn.setToolTip("复制脚本")
toolbarLayout.addWidget(self._copyBtn)
self.copyBtn.setFixedSize(25, 25)
self.copyBtn.setToolTip("复制脚本")
toolbarLayout.addWidget(self.copyBtn)
layout.addLayout(toolbarLayout)
self._textEdit = QPlainTextEdit(self)
self._textEdit.setLineWrapMode(
self.textEdit = QPlainTextEdit(self)
self.textEdit.setLineWrapMode(
QPlainTextEdit.LineWrapMode.NoWrap
)
self._textEdit.setStyleSheet(
self.textEdit.setStyleSheet(
"QPlainTextEdit {"
" font-family: 'Courier New', 'Consolas', monospace;"
f" font-size: {self._fontSize}px;"
"}"
)
layout.addWidget(self._textEdit)
layout.addWidget(self.textEdit)
self._createButtonPanel(layout)
self._btnBox = QDialogButtonBox(
self.btnBox = QDialogButtonBox(
QDialogButtonBox.StandardButton.Ok |
QDialogButtonBox.StandardButton.Cancel
)
self._btnBox.button(
QDialogButtonBox.StandardButton.Ok
).setText("保存")
self._btnBox.button(
QDialogButtonBox.StandardButton.Cancel
).setText("取消")
layout.addWidget(self._btnBox)
self.btnBox.button(QDialogButtonBox.StandardButton.Ok).setText("确定")
self.btnBox.button(QDialogButtonBox.StandardButton.Cancel).setText("取消")
layout.addWidget(self.btnBox)
def _createButtonPanel(
self,
@@ -271,6 +265,7 @@ class ALAutoScriptEditDialog(QDialog):
tab_widget.addTab(var_widget, "变量")
parent_layout.addWidget(tab_widget)
def _addButtonsToGrid(
self,
grid_layout,
@@ -308,43 +303,43 @@ class ALAutoScriptEditDialog(QDialog):
template = btn.property("template")
if not template:
return
cursor = self._textEdit.textCursor()
cursor = self.textEdit.textCursor()
cursor.insertText(template)
def connectSignals(
self
):
self._btnBox.accepted.connect(self.accept)
self._btnBox.rejected.connect(self.reject)
self._zoomInBtn.clicked.connect(self.onZoomIn)
self._zoomOutBtn.clicked.connect(self.onZoomOut)
self._zoomResetBtn.clicked.connect(self.onZoomReset)
self._copyBtn.clicked.connect(self.onCopy)
self.btnBox.accepted.connect(self.accept)
self.btnBox.rejected.connect(self.reject)
self.zoomInBtn.clicked.connect(self.onZoomIn)
self.zoomOutBtn.clicked.connect(self.onZoomOut)
self.zoomResetBtn.clicked.connect(self.onZoomReset)
self.copyBtn.clicked.connect(self.onCopy)
def getScript(
self
) -> str:
return self._textEdit.toPlainText()
return self.textEdit.toPlainText()
def updateFontSize(
self
):
font = self._textEdit.font()
font = self.textEdit.font()
font.setPointSize(self._fontSize)
self._textEdit.setFont(font)
self._textEdit.setStyleSheet(
self.textEdit.setFont(font)
self.textEdit.setStyleSheet(
"QPlainTextEdit {"
" font-family: 'Courier New', 'Consolas', monospace;"
f" font-size: {self._fontSize}px;"
"}"
)
self._zoomLabel.setText(f"{self._fontSize}px")
self.zoomLabel.setText(f"{self._fontSize}px")
@Slot()
def onZoomIn(
@@ -354,7 +349,6 @@ class ALAutoScriptEditDialog(QDialog):
self._fontSize = min(self._fontSize + 2, 40)
self.updateFontSize()
@Slot()
def onZoomOut(
self
@@ -363,7 +357,6 @@ class ALAutoScriptEditDialog(QDialog):
self._fontSize = max(self._fontSize - 2, 8)
self.updateFontSize()
@Slot()
def onZoomReset(
self
@@ -372,19 +365,18 @@ class ALAutoScriptEditDialog(QDialog):
self._fontSize = 13
self.updateFontSize()
@Slot()
def onCopy(
self
):
clipboard = QApplication.clipboard()
clipboard.setText(self._textEdit.toPlainText())
original = self._copyBtn.text()
self._copyBtn.setText("已复制")
self._copyBtn.setEnabled(False)
clipboard.setText(self.textEdit.toPlainText())
original = self.copyBtn.text()
self.copyBtn.setText("已复制")
self.copyBtn.setEnabled(False)
from PySide6.QtCore import QTimer
QTimer.singleShot(2000, lambda: (
self._copyBtn.setText(original),
self._copyBtn.setEnabled(True)
self.copyBtn.setText(original),
self.copyBtn.setEnabled(True)
))
+11 -11
View File
@@ -79,9 +79,9 @@ class ConditionalBlock(QGroupBox):
condLayout.setContentsMargins(4, 4, 4, 4)
condLayout.setSpacing(6)
self._condRowsLayout = QVBoxLayout()
self._condRowsLayout.setSpacing(4)
condLayout.addLayout(self._condRowsLayout)
self.condRowsLayout = QVBoxLayout()
self.condRowsLayout.setSpacing(4)
condLayout.addLayout(self.condRowsLayout)
self.addCondBtn = QPushButton("+ 添加条件", self.conditionWidget)
self.addCondBtn.setFixedHeight(25)
condLayout.addWidget(self.addCondBtn)
@@ -89,9 +89,9 @@ class ConditionalBlock(QGroupBox):
self.actionLabel = QLabel("执行步骤:", self)
self.actionLabel.setFixedHeight(25)
mainLayout.addWidget(self.actionLabel)
self._actionsLayout = QVBoxLayout()
self._actionsLayout.setSpacing(4)
mainLayout.addLayout(self._actionsLayout)
self.actionsLayout = QVBoxLayout()
self.actionsLayout.setSpacing(4)
mainLayout.addLayout(self.actionsLayout)
self.addActionBtn = QPushButton("+ 添加执行步骤", self)
self.addActionBtn.setFixedHeight(25)
mainLayout.addWidget(self.addActionBtn)
@@ -116,7 +116,7 @@ class ConditionalBlock(QGroupBox):
isFirst=True, parent=self
)
self._conditionRows.append(row)
self._condRowsLayout.addWidget(row)
self.condRowsLayout.addWidget(row)
def addConditionRow(
@@ -129,7 +129,7 @@ class ConditionalBlock(QGroupBox):
)
row.deleteBtn.clicked.connect(lambda: self.removeConditionRow(row))
self._conditionRows.append(row)
self._condRowsLayout.addWidget(row)
self.condRowsLayout.addWidget(row)
def removeConditionRow(
@@ -139,7 +139,7 @@ class ConditionalBlock(QGroupBox):
if row in self._conditionRows and len(self._conditionRows) > 1:
self._conditionRows.remove(row)
self._condRowsLayout.removeWidget(row)
self.condRowsLayout.removeWidget(row)
row.hide()
row.deleteLater()
@@ -151,7 +151,7 @@ class ConditionalBlock(QGroupBox):
step = ActionStepFrame(self._varMgr, self.blockIndex, parent=self)
step.deleteBtn.clicked.connect(lambda: self.removeActionStep(step))
self._actionWidgets.append(step)
self._actionsLayout.addWidget(step)
self.actionsLayout.addWidget(step)
def removeActionStep(
@@ -161,7 +161,7 @@ class ConditionalBlock(QGroupBox):
if step in self._actionWidgets:
self._actionWidgets.remove(step)
self._actionsLayout.removeWidget(step)
self.actionsLayout.removeWidget(step)
step.hide()
step.deleteLater()
+18 -18
View File
@@ -46,7 +46,7 @@ class ALAutoScriptOrchDialog(QDialog):
self.loadFromScript(existingScript)
else:
self.addBlock()
self._scrollLayout.addStretch()
self.scrollLayout.addStretch()
def setupUi(
@@ -61,8 +61,8 @@ class ALAutoScriptOrchDialog(QDialog):
scroll.setWidgetResizable(True)
scroll.setFrameShape(QFrame.NoFrame)
scrollContent = QWidget()
self._scrollLayout = QVBoxLayout(scrollContent)
self._scrollLayout.setSpacing(5)
self.scrollLayout = QVBoxLayout(scrollContent)
self.scrollLayout.setSpacing(5)
scroll.setWidget(scrollContent)
mainLayout.addWidget(scroll)
self.addBlockBtn = QPushButton("+ 添加判断块")
@@ -108,16 +108,16 @@ class ALAutoScriptOrchDialog(QDialog):
block.addActionStep()
self._blocks.append(block)
self._updateBlockTypeRestrictions()
if self._scrollLayout.count() > 0:
lastItem = self._scrollLayout.itemAt(
self._scrollLayout.count() - 1
if self.scrollLayout.count() > 0:
lastItem = self.scrollLayout.itemAt(
self.scrollLayout.count() - 1
)
if lastItem and lastItem.spacerItem():
self._scrollLayout.insertWidget(
self._scrollLayout.count() - 1, block
self.scrollLayout.insertWidget(
self.scrollLayout.count() - 1, block
)
return
self._scrollLayout.addWidget(block)
self.scrollLayout.addWidget(block)
def removeBlock(
@@ -130,7 +130,7 @@ class ALAutoScriptOrchDialog(QDialog):
return
if block in self._blocks:
self._blocks.remove(block)
self._scrollLayout.removeWidget(block)
self.scrollLayout.removeWidget(block)
block.hide()
block.deleteLater()
for i, blk in enumerate(self._blocks):
@@ -153,12 +153,12 @@ class ALAutoScriptOrchDialog(QDialog):
for block in self._blocks:
blockType = block.getBlockType()
if blockType == "IF" and prevType is not None:
parts.append("ENDIF")
parts.append("END IF")
lines = block.toScriptLines()
parts.extend(lines)
prevType = blockType
if self._blocks and self._blocks[0].getBlockType() == "IF":
parts.append("ENDIF")
parts.append("END IF")
return "\n".join(parts)
@Slot()
@@ -200,8 +200,8 @@ class ALAutoScriptOrchDialog(QDialog):
typeIdxMap = {"IF": 0, "ELSE IF": 1, "ELSE": 2}
parsedBlocks = parseBlocks(script)
self._blocks.clear()
while self._scrollLayout.count():
item = self._scrollLayout.takeAt(0)
while self.scrollLayout.count():
item = self.scrollLayout.takeAt(0)
if item.widget():
item.widget().deleteLater()
try:
@@ -222,8 +222,8 @@ class ALAutoScriptOrchDialog(QDialog):
self._parseConditions(block, condition)
except Exception:
self._blocks.clear()
while self._scrollLayout.count():
item = self._scrollLayout.takeAt(0)
while self.scrollLayout.count():
item = self.scrollLayout.takeAt(0)
if item.widget():
item.widget().deleteLater()
self._updateBlockTypeRestrictions()
@@ -255,7 +255,7 @@ class ALAutoScriptOrchDialog(QDialog):
allLogics.append(".AND.")
allSubConds.append(ap)
for row in list(block._conditionRows):
block._condRowsLayout.removeWidget(row)
block.condRowsLayout.removeWidget(row)
row.hide()
row.deleteLater()
block._conditionRows.clear()
@@ -278,7 +278,7 @@ class ALAutoScriptOrchDialog(QDialog):
row.logicCombo.setCurrentIndex(li)
break
block._conditionRows.append(row)
block._condRowsLayout.addWidget(row)
block.condRowsLayout.addWidget(row)
subUp = subCond.upper()
if subUp in (".TRUE.", ".FALSE."):
row.loadFromParts(subUp, "", "")
+36 -62
View File
@@ -3,7 +3,11 @@ Helper utilities and constants for the AutoScript orchestration dialog.
"""
import re
from PySide6.QtCore import QObject, QDate, QTime
from PySide6.QtCore import (
QObject,
QDate,
QTime
)
from PySide6.QtWidgets import (
QComboBox,
QDateEdit,
@@ -18,7 +22,14 @@ from PySide6.QtWidgets import (
QWidget,
)
from autoscript import ALL_VARIABLES
from autoscript import (
ALL_VARIABLES,
splitTopLevel
)
from autoscript.ASOperator import (
ARITH_TYPES,
COMPARISON_OPERATORS
)
VAR_TYPE_ORDER = [
@@ -40,14 +51,19 @@ PRESET_VARIABLES = [
PRESET_NAMES = {
p["name"] for p in PRESET_VARIABLES
}
COMPARE_OPERATORS = sorted([
("等于", ".EQ."),
("不等于", ".NEQ."),
("大于", ".BGT."),
("小于", ".BLT."),
("大于等于", ".BGE."),
("小于等于", ".BLE."),
], key=lambda x: len(x[1]), reverse=True)
# Operator display names (UI-specific), symbols derived from engine
_COMPARE_DISPLAY_MAP = {
".EQ.": "等于",
".NEQ.": "不等于",
".BGT.": "大于",
".BLT.": "小于",
".BGE.": "大于等于",
".BLE.": "小于等于",
}
COMPARE_OPERATORS = sorted(
[(name, op) for op, name in _COMPARE_DISPLAY_MAP.items() if op in COMPARISON_OPERATORS],
key=lambda x: len(x[1]), reverse=True
)
LOGIC_OPERATORS = [
("并且 (.AND.)", ".AND."),
("或者 (.OR.)", ".OR."),
@@ -57,12 +73,6 @@ ACTION_TYPES = [
("增加", "add"),
("减少", "sub"),
]
ARITH_TYPES = {
"Date",
"Time",
"Int",
"Float"
}
DATE_RELATIVE_OPTIONS = [
("前天", "day_before_yesterday"),
("昨天", "yesterday"),
@@ -310,21 +320,17 @@ class _DateInputContainer(QWidget):
):
s = expr.strip().upper()
if s == "CURRENT_DATE - 2":
_RELATIVE_MAP = {
"CURRENT_DATE": 0, "TODAY": 0,
"CURRENT_DATE + 1": 1, "TOMORROW": 1,
"CURRENT_DATE + 2": 2,
"CURRENT_DATE - 1": 3,
"CURRENT_DATE - 2": 4,
}
idx = _RELATIVE_MAP.get(s)
if idx is not None:
self._modeCombo.setCurrentIndex(0)
self._relCombo.setCurrentIndex(4)
elif s == "CURRENT_DATE - 1":
self._modeCombo.setCurrentIndex(0)
self._relCombo.setCurrentIndex(3)
elif s in ("CURRENT_DATE", "TODAY"):
self._modeCombo.setCurrentIndex(0)
self._relCombo.setCurrentIndex(0)
elif s == "CURRENT_DATE + 1" or s == "TOMORROW":
self._modeCombo.setCurrentIndex(0)
self._relCombo.setCurrentIndex(1)
elif s == "CURRENT_DATE + 2":
self._modeCombo.setCurrentIndex(0)
self._relCombo.setCurrentIndex(2)
self._relCombo.setCurrentIndex(idx)
elif s.startswith("DATE("):
self._modeCombo.setCurrentIndex(1)
m = re.match(r"DATE\((\d{4}-\d{2}-\d{2})\)", s)
@@ -585,38 +591,6 @@ def encodeValueStr(
return raw_value
def splitTopLevel(
text: str,
delimiter: str
) -> list:
parts = []
depth = 0
buf = ""
i = 0
textUpper = text.upper()
delimUpper = delimiter.upper()
dlen = len(delimUpper)
while i < len(text):
if text[i] == "(":
depth += 1
buf += text[i]
elif text[i] == ")":
depth -= 1
buf += text[i]
elif depth == 0 and textUpper[i:i + dlen] == delimUpper:
parts.append(buf)
buf = ""
i += dlen
continue
else:
buf += text[i]
i += 1
if buf.strip():
parts.append(buf)
return parts
def stripOuterParens(
s: str
) -> str:
+49 -51
View File
@@ -80,21 +80,21 @@ class ConditionRowFrame(QFrame):
("变量", "variable"),
], min_width=70, parent=self)
layout.addWidget(self._compTypeCombo)
self._rhsStack = QStackedWidget(self)
self._rhsStack.setFixedHeight(25)
self._literalStack = QStackedWidget(self)
self._literalStack.setFixedHeight(25)
self._literalWidgets = {}
self.rhsStack = QStackedWidget(self)
self.rhsStack.setFixedHeight(25)
self.literalStack = QStackedWidget(self)
self.literalStack.setFixedHeight(25)
self.literalWidgets = {}
for vt in VAR_TYPE_ORDER:
w = makeValueWidget(vt, self._literalStack)
self._literalWidgets[vt] = w
self._literalStack.addWidget(w)
self._literalStack.setCurrentWidget(self._literalWidgets.get("String"))
self._rhsStack.addWidget(self._literalStack)
w = makeValueWidget(vt, self.literalStack)
self.literalWidgets[vt] = w
self.literalStack.addWidget(w)
self.literalStack.setCurrentWidget(self.literalWidgets.get("String"))
self.rhsStack.addWidget(self.literalStack)
self.rhsVarCombo = makeVarRefCombo(self)
self._rhsStack.addWidget(self.rhsVarCombo)
self._rhsStack.setCurrentIndex(0)
layout.addWidget(self._rhsStack)
self.rhsStack.addWidget(self.rhsVarCombo)
self.rhsStack.setCurrentIndex(0)
layout.addWidget(self.rhsStack)
if not self._isFirst:
self.deleteBtn = QPushButton("×", self)
self.deleteBtn.setFixedSize(25, 25)
@@ -127,7 +127,6 @@ class ConditionRowFrame(QFrame):
self.leftVarCombo.currentIndexChanged.connect(self.onLeftVarChanged)
self._compTypeCombo.currentIndexChanged.connect(self.onCompTypeChanged)
@Slot(int)
def onLeftVarChanged(
self,
@@ -148,10 +147,9 @@ class ConditionRowFrame(QFrame):
vartype: str
):
if vartype not in self._literalWidgets:
if vartype not in self.literalWidgets:
vartype = "String"
self._literalStack.setCurrentWidget(self._literalWidgets[vartype])
self.literalStack.setCurrentWidget(self.literalWidgets[vartype])
@Slot(int)
def onCompTypeChanged(
@@ -160,7 +158,7 @@ class ConditionRowFrame(QFrame):
):
isVar = (self._compTypeCombo.currentData() == "variable")
self._rhsStack.setCurrentIndex(1 if isVar else 0)
self.rhsStack.setCurrentIndex(1 if isVar else 0)
if isVar:
self.populateRhsVarCombo()
@@ -191,7 +189,7 @@ class ConditionRowFrame(QFrame):
if rhsText:
return f"{name} {opSym} {rhsText}"
return ""
w = self._literalWidgets.get(vartype)
w = self.literalWidgets.get(vartype)
if w:
rawVal = getValueFromWidget(w)
encoded = encodeValueStr(rawVal, vartype)
@@ -232,7 +230,7 @@ class ConditionRowFrame(QFrame):
self.rhsVarCombo.setCurrentIndex(self.rhsVarCombo.count() - 1)
else:
self._compTypeCombo.setCurrentIndex(0)
w = self._literalWidgets.get(vartype)
w = self.literalWidgets.get(vartype)
if w:
setWidgetValue(w, vartype, valueExpr)
@@ -291,15 +289,15 @@ class ActionStepFrame(QFrame):
self.buildTargetCombo()
layout.addWidget(self.targetCombo)
layout.addWidget(makeLabel("", self))
self._valueSrcCombo = makeComboWidget([
self.valueSrcCombo = makeComboWidget([
("特定值", "literal"),
("变量", "variable"),
], min_width=70, parent=self)
layout.addWidget(self._valueSrcCombo)
self._valueStack = QStackedWidget(self)
self._valueStack.setFixedHeight(25)
layout.addWidget(self.valueSrcCombo)
self.valueStack = QStackedWidget(self)
self.valueStack.setFixedHeight(25)
self.initValueStacks()
layout.addWidget(self._valueStack)
layout.addWidget(self.valueStack)
self.existingVarCombo = makeVarRefCombo(self)
self.existingVarCombo.setVisible(False)
layout.addWidget(self.existingVarCombo)
@@ -335,16 +333,16 @@ class ActionStepFrame(QFrame):
self._literalWidgets = {}
self._offsetWidgets = {}
for vt in VAR_TYPE_ORDER:
self._literalWidgets[vt] = makeValueWidget(vt, self._valueStack)
self._valueStack.addWidget(self._literalWidgets[vt])
self._literalWidgets[vt] = makeValueWidget(vt, self.valueStack)
self.valueStack.addWidget(self._literalWidgets[vt])
if vt in ARITH_TYPES:
self._offsetWidgets[vt] = makeOffsetWidget(vt, self._valueStack)
self._valueStack.addWidget(self._offsetWidgets[vt])
self._offsetWidgets[vt] = makeOffsetWidget(vt, self.valueStack)
self.valueStack.addWidget(self._offsetWidgets[vt])
else:
lbl = QLabel("(不支持该操作)", self._valueStack)
lbl = QLabel("(不支持该操作)", self.valueStack)
lbl.setFixedHeight(25)
self._offsetWidgets[vt] = lbl
self._valueStack.addWidget(lbl)
self.valueStack.addWidget(lbl)
def connectSignals(
@@ -353,7 +351,7 @@ class ActionStepFrame(QFrame):
self.opTypeCombo.currentIndexChanged.connect(self.onOpTypeChanged)
self.targetCombo.currentIndexChanged.connect(self.onTargetChanged)
self._valueSrcCombo.currentIndexChanged.connect(self.onValueSrcChanged)
self.valueSrcCombo.currentIndexChanged.connect(self.onValueSrcChanged)
@Slot(int)
def onTargetChanged(
@@ -369,7 +367,7 @@ class ActionStepFrame(QFrame):
_, vartype = data
self._currentTargetType = vartype
self.updateRHSWidget()
self.onValueSrcChanged(self._valueSrcCombo.currentIndex())
self.onValueSrcChanged(self.valueSrcCombo.currentIndex())
@Slot(int)
def onOpTypeChanged(
@@ -388,11 +386,11 @@ class ActionStepFrame(QFrame):
isArith = (op in ("add", "sub"))
actualType = self._currentTargetType
if isArith and actualType in self._offsetWidgets:
self._valueStack.setCurrentWidget(self._offsetWidgets[actualType])
self.valueStack.setCurrentWidget(self._offsetWidgets[actualType])
elif actualType in self._literalWidgets:
self._valueStack.setCurrentWidget(self._literalWidgets[actualType])
self.valueStack.setCurrentWidget(self._literalWidgets[actualType])
else:
self._valueStack.setCurrentWidget(self._literalWidgets.get("String"))
self.valueStack.setCurrentWidget(self._literalWidgets.get("String"))
@Slot(int)
def onValueSrcChanged(
@@ -400,8 +398,8 @@ class ActionStepFrame(QFrame):
idx
):
isVar = (self._valueSrcCombo.currentData() == "variable")
self._valueStack.setVisible(not isVar)
isVar = (self.valueSrcCombo.currentData() == "variable")
self.valueStack.setVisible(not isVar)
self.existingVarCombo.setVisible(isVar)
if isVar:
self._varMgr.populateCombo(self.existingVarCombo)
@@ -437,20 +435,20 @@ class ActionStepFrame(QFrame):
return f" SET {target} = {encoded}"
elif op == "add":
vartype = self._currentTargetType
if vartype == "Date" and hasattr(self._valueStack.currentWidget(), "getOffsetDays"):
days = self._valueStack.currentWidget().getOffsetDays()
if vartype == "Date" and hasattr(self.valueStack.currentWidget(), "getOffsetDays"):
days = self.valueStack.currentWidget().getOffsetDays()
return f" {target} .ADD. {days}"
if vartype == "Time" and hasattr(self._valueStack.currentWidget(), "getOffsetHours"):
hours = self._valueStack.currentWidget().getOffsetHours()
if vartype == "Time" and hasattr(self.valueStack.currentWidget(), "getOffsetHours"):
hours = self.valueStack.currentWidget().getOffsetHours()
return f" {target} .ADD. {hours}"
return f" {target} .ADD. {rawVal}"
elif op == "sub":
vartype = self._currentTargetType
if vartype == "Date" and hasattr(self._valueStack.currentWidget(), "getOffsetDays"):
days = self._valueStack.currentWidget().getOffsetDays()
if vartype == "Date" and hasattr(self.valueStack.currentWidget(), "getOffsetDays"):
days = self.valueStack.currentWidget().getOffsetDays()
return f" {target} .SUB. {days}"
if vartype == "Time" and hasattr(self._valueStack.currentWidget(), "getOffsetHours"):
hours = self._valueStack.currentWidget().getOffsetHours()
if vartype == "Time" and hasattr(self.valueStack.currentWidget(), "getOffsetHours"):
hours = self.valueStack.currentWidget().getOffsetHours()
return f" {target} .SUB. {hours}"
return f" {target} .SUB. {rawVal}"
return ""
@@ -460,9 +458,9 @@ class ActionStepFrame(QFrame):
self
) -> str:
if self._valueSrcCombo.currentData() == "variable":
if self.valueSrcCombo.currentData() == "variable":
return self.existingVarCombo.currentText().strip()
w = self._valueStack.currentWidget()
w = self.valueStack.currentWidget()
if w:
return getValueFromWidget(w)
return ""
@@ -504,7 +502,7 @@ class ActionStepFrame(QFrame):
return
up = s.upper()
if isVarReference(s):
self._valueSrcCombo.setCurrentIndex(1)
self.valueSrcCombo.setCurrentIndex(1)
self._varMgr.populateCombo(self.existingVarCombo)
idx = self._varMgr.findExactNameEntry(self.existingVarCombo, up)
if idx >= 0:
@@ -513,8 +511,8 @@ class ActionStepFrame(QFrame):
self.existingVarCombo.addItem(up, (up, "String"))
self.existingVarCombo.setCurrentIndex(self.existingVarCombo.count() - 1)
else:
self._valueSrcCombo.setCurrentIndex(0)
w = self._valueStack.currentWidget()
self.valueSrcCombo.setCurrentIndex(0)
w = self.valueStack.currentWidget()
if w:
setWidgetValue(w, self._currentTargetType, expr)
+2
View File
@@ -31,6 +31,7 @@ class ALSeatFrame(QFrame):
self.setupUi()
def setupUi(
self
):
@@ -54,6 +55,7 @@ class ALSeatFrame(QFrame):
self.Label.setAlignment(Qt.AlignCenter)
self.Label.setGeometry(0, 0, 60, 40)
def mousePressEvent(
self,
event
+4 -2
View File
@@ -58,6 +58,8 @@ class ALTimerTaskAddDialog(QDialog, Ui_ALTimerTaskAddDialog):
self.TimerTypeComboBox.setCurrentIndex(0)
self.SpecificTimerWidget = QWidget()
self.SpecificTimerLayout = QHBoxLayout(self.SpecificTimerWidget)
self.SpecificTimerLayout.setContentsMargins(0, 0, 0, 0)
self.SpecificTimerLayout.setSpacing(5)
self.SpecificTimerLayout.addWidget(QLabel("定时时间:"))
self.SpecificDateTimeEdit = QDateTimeEdit()
self.SpecificDateTimeEdit.setCalendarPopup(True)
@@ -69,6 +71,8 @@ class ALTimerTaskAddDialog(QDialog, Ui_ALTimerTaskAddDialog):
self.RelativeTimerWidget = QWidget()
self.RelativeTimerLayout = QHBoxLayout(self.RelativeTimerWidget)
self.RelativeTimerLayout.setContentsMargins(0, 0, 0, 0)
self.RelativeTimerLayout.setSpacing(5)
self.RelativeTimerLayout.addWidget(QLabel("相对时间:"))
self.RelativeDaySpinBox = QSpinBox()
self.RelativeDaySpinBox.setMinimum(0)
@@ -325,5 +329,3 @@ class ALTimerTaskAddDialog(QDialog, Ui_ALTimerTaskAddDialog):
QDesktopServices.openUrl(
QUrl("https://www.autolibrary.kenanzhu.com/manuals/autoscript")
)
+2 -3
View File
@@ -28,15 +28,14 @@ class ALTimerTaskHistoryDialog(QDialog):
):
super().__init__(parent)
self.__task_data = task_data
self.__history = task_data.get("repeat_history", [])
self.modifyUi()
self.setupUi()
self.connectSignals()
def modifyUi(
def setupUi(
self
):