Add global delay settings to Scenario and MainWindow for customizable action delays
This commit is contained in:
@@ -650,25 +650,39 @@ class Scenario:
|
|||||||
Represents a scenario, which consists of a name and a list of steps.
|
Represents a scenario, which consists of a name and a list of steps.
|
||||||
Handles saving/loading scenario data to/from disk.
|
Handles saving/loading scenario data to/from disk.
|
||||||
"""
|
"""
|
||||||
def __init__(self, name, steps=None):
|
def __init__(self, name, steps=None, global_delay=None):
|
||||||
"""
|
"""
|
||||||
Initialize a Scenario with a name and optional steps.
|
Initialize a Scenario with a name, optional steps, and global delay settings.
|
||||||
"""
|
"""
|
||||||
self.name = name
|
self.name = name
|
||||||
self.steps = steps or [] # List of Step dicts
|
self.steps = steps or [] # List of Step dicts
|
||||||
|
# Global delay settings - delays in milliseconds
|
||||||
|
self.global_delay = global_delay or {
|
||||||
|
'before_action': 0, # Delay before each action in ms
|
||||||
|
'after_action': 0 # Delay after each action in ms
|
||||||
|
}
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
"""
|
"""
|
||||||
Return a dictionary representation of the scenario.
|
Return a dictionary representation of the scenario.
|
||||||
"""
|
"""
|
||||||
return {'name': self.name, 'steps': self.steps}
|
return {
|
||||||
|
'name': self.name,
|
||||||
|
'steps': self.steps,
|
||||||
|
'global_delay': self.global_delay
|
||||||
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_dict(data):
|
def from_dict(data):
|
||||||
"""
|
"""
|
||||||
Create a Scenario object from a dictionary.
|
Create a Scenario object from a dictionary.
|
||||||
"""
|
"""
|
||||||
return Scenario(data['name'], data.get('steps', []))
|
# Handle backward compatibility for scenarios without global_delay
|
||||||
|
global_delay = data.get('global_delay', {
|
||||||
|
'before_action': 0,
|
||||||
|
'after_action': 0
|
||||||
|
})
|
||||||
|
return Scenario(data['name'], data.get('steps', []), global_delay)
|
||||||
|
|
||||||
def get_scenario_dir(self):
|
def get_scenario_dir(self):
|
||||||
"""
|
"""
|
||||||
@@ -1454,10 +1468,19 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
def _perform_step_action(self, action, loc, shape):
|
def _perform_step_action(self, action, loc, shape):
|
||||||
"""
|
"""
|
||||||
Perform a single step action (click, key, scroll, delay) at the given location.
|
Perform a single step action (click, key, scroll, delay) at the given location.
|
||||||
|
Applies global delays before and after each action.
|
||||||
"""
|
"""
|
||||||
act_type = action['type']
|
act_type = action['type']
|
||||||
params = action['params']
|
params = action['params']
|
||||||
logger.debug(f'Performing step action: {act_type}, params={params}, loc={loc}, shape={shape}')
|
logger.debug(f'Performing step action: {act_type}, params={params}, loc={loc}, shape={shape}')
|
||||||
|
|
||||||
|
# Apply global before-action delay
|
||||||
|
if self.current_scenario and self.current_scenario.global_delay:
|
||||||
|
before_delay = self.current_scenario.global_delay.get('before_action', 0)
|
||||||
|
if before_delay > 0:
|
||||||
|
logger.debug(f'Applying global before-action delay: {before_delay}ms')
|
||||||
|
time.sleep(before_delay / 1000.0)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if act_type == 'click':
|
if act_type == 'click':
|
||||||
pos_type = params.get('pos_type', 'center')
|
pos_type = params.get('pos_type', 'center')
|
||||||
@@ -1493,6 +1516,13 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
time.sleep(duration)
|
time.sleep(duration)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f'Error performing step action {act_type}: {e}')
|
logger.error(f'Error performing step action {act_type}: {e}')
|
||||||
|
|
||||||
|
# Apply global after-action delay
|
||||||
|
if self.current_scenario and self.current_scenario.global_delay:
|
||||||
|
after_delay = self.current_scenario.global_delay.get('after_action', 0)
|
||||||
|
if after_delay > 0:
|
||||||
|
logger.debug(f'Applying global after-action delay: {after_delay}ms')
|
||||||
|
time.sleep(after_delay / 1000.0)
|
||||||
def set_state(self, text):
|
def set_state(self, text):
|
||||||
"""
|
"""
|
||||||
Update the state label in the UI.
|
Update the state label in the UI.
|
||||||
@@ -3048,6 +3078,39 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
self.combo.currentIndexChanged.connect(self._log_combo_scenario)
|
self.combo.currentIndexChanged.connect(self._log_combo_scenario)
|
||||||
scenario_group_layout.addWidget(self.combo)
|
scenario_group_layout.addWidget(self.combo)
|
||||||
|
|
||||||
|
# Global delay settings
|
||||||
|
global_delay_layout = QtWidgets.QHBoxLayout()
|
||||||
|
global_delay_layout.setSpacing(8)
|
||||||
|
global_delay_layout.setContentsMargins(0, 5, 0, 5)
|
||||||
|
|
||||||
|
# Before action delay
|
||||||
|
before_label = QtWidgets.QLabel('Before Action (ms):')
|
||||||
|
before_label.setMinimumWidth(100)
|
||||||
|
self.before_action_spinbox = QtWidgets.QSpinBox()
|
||||||
|
self.before_action_spinbox.setRange(0, 10000)
|
||||||
|
self.before_action_spinbox.setValue(0)
|
||||||
|
self.before_action_spinbox.setSuffix(' ms')
|
||||||
|
self.before_action_spinbox.setToolTip('Delay before each action in milliseconds')
|
||||||
|
self.before_action_spinbox.valueChanged.connect(self._on_global_delay_changed)
|
||||||
|
|
||||||
|
# After action delay
|
||||||
|
after_label = QtWidgets.QLabel('After Action (ms):')
|
||||||
|
after_label.setMinimumWidth(95)
|
||||||
|
self.after_action_spinbox = QtWidgets.QSpinBox()
|
||||||
|
self.after_action_spinbox.setRange(0, 10000)
|
||||||
|
self.after_action_spinbox.setValue(0)
|
||||||
|
self.after_action_spinbox.setSuffix(' ms')
|
||||||
|
self.after_action_spinbox.setToolTip('Delay after each action in milliseconds')
|
||||||
|
self.after_action_spinbox.valueChanged.connect(self._on_global_delay_changed)
|
||||||
|
|
||||||
|
global_delay_layout.addWidget(before_label)
|
||||||
|
global_delay_layout.addWidget(self.before_action_spinbox)
|
||||||
|
global_delay_layout.addWidget(after_label)
|
||||||
|
global_delay_layout.addWidget(self.after_action_spinbox)
|
||||||
|
global_delay_layout.addStretch()
|
||||||
|
|
||||||
|
scenario_group_layout.addLayout(global_delay_layout)
|
||||||
|
|
||||||
# Window selection dropdown
|
# Window selection dropdown
|
||||||
self.window_combo = QtWidgets.QComboBox()
|
self.window_combo = QtWidgets.QComboBox()
|
||||||
self.window_combo.setMinimumWidth(90)
|
self.window_combo.setMinimumWidth(90)
|
||||||
@@ -3288,6 +3351,23 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
logger.info(f"UI: Scenario combo changed to index {idx} ({self.combo.currentText()})")
|
logger.info(f"UI: Scenario combo changed to index {idx} ({self.combo.currentText()})")
|
||||||
self.select_scenario()
|
self.select_scenario()
|
||||||
|
|
||||||
|
def _on_global_delay_changed(self):
|
||||||
|
"""
|
||||||
|
Handle changes to global delay settings and auto-save scenario.
|
||||||
|
"""
|
||||||
|
if self.current_scenario:
|
||||||
|
before_delay = self.before_action_spinbox.value()
|
||||||
|
after_delay = self.after_action_spinbox.value()
|
||||||
|
|
||||||
|
self.current_scenario.global_delay = {
|
||||||
|
'before_action': before_delay,
|
||||||
|
'after_action': after_delay
|
||||||
|
}
|
||||||
|
|
||||||
|
# Auto-save the scenario with new delay settings
|
||||||
|
self.current_scenario.save()
|
||||||
|
logger.info(f"Global delay updated: before={before_delay}ms, after={after_delay}ms")
|
||||||
|
|
||||||
def _log_combo_window(self, idx):
|
def _log_combo_window(self, idx):
|
||||||
"""
|
"""
|
||||||
Log window combo box change and call save_selected_window.
|
Log window combo box change and call save_selected_window.
|
||||||
@@ -3984,11 +4064,35 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
def refresh_lists(self):
|
def refresh_lists(self):
|
||||||
"""
|
"""
|
||||||
Refresh the step list widget with the current scenario's steps.
|
Refresh the step list widget with the current scenario's steps.
|
||||||
|
Also updates global delay controls.
|
||||||
"""
|
"""
|
||||||
logger.debug('Refreshing steps list.')
|
logger.debug('Refreshing steps list.')
|
||||||
self.steps_list.clear()
|
self.steps_list.clear()
|
||||||
if not self.current_scenario:
|
|
||||||
|
# Update global delay controls
|
||||||
|
if self.current_scenario:
|
||||||
|
# Temporarily disconnect signals to prevent triggering auto-save during loading
|
||||||
|
self.before_action_spinbox.valueChanged.disconnect()
|
||||||
|
self.after_action_spinbox.valueChanged.disconnect()
|
||||||
|
|
||||||
|
# Load global delay settings
|
||||||
|
global_delay = self.current_scenario.global_delay
|
||||||
|
self.before_action_spinbox.setValue(global_delay.get('before_action', 0))
|
||||||
|
self.after_action_spinbox.setValue(global_delay.get('after_action', 0))
|
||||||
|
|
||||||
|
# Reconnect signals
|
||||||
|
self.before_action_spinbox.valueChanged.connect(self._on_global_delay_changed)
|
||||||
|
self.after_action_spinbox.valueChanged.connect(self._on_global_delay_changed)
|
||||||
|
else:
|
||||||
|
# Reset to default values when no scenario is selected
|
||||||
|
self.before_action_spinbox.valueChanged.disconnect()
|
||||||
|
self.after_action_spinbox.valueChanged.disconnect()
|
||||||
|
self.before_action_spinbox.setValue(0)
|
||||||
|
self.after_action_spinbox.setValue(0)
|
||||||
|
self.before_action_spinbox.valueChanged.connect(self._on_global_delay_changed)
|
||||||
|
self.after_action_spinbox.valueChanged.connect(self._on_global_delay_changed)
|
||||||
return
|
return
|
||||||
|
|
||||||
for step in self.current_scenario.steps:
|
for step in self.current_scenario.steps:
|
||||||
name = step.get('name', 'Unnamed Step')
|
name = step.get('name', 'Unnamed Step')
|
||||||
cond = step.get('condition', 'OR')
|
cond = step.get('condition', 'OR')
|
||||||
|
|||||||
Reference in New Issue
Block a user