add debounce delay for start/stop button
This commit is contained in:
@@ -37,21 +37,40 @@ if not os.path.exists(CONFIG_DIR):
|
||||
os.makedirs(CONFIG_DIR)
|
||||
|
||||
class Scenario:
|
||||
"""
|
||||
Represents a scenario, which consists of a name and a list of steps.
|
||||
Handles saving/loading scenario data to/from disk.
|
||||
"""
|
||||
def __init__(self, name, steps=None):
|
||||
"""
|
||||
Initialize a Scenario with a name and optional steps.
|
||||
"""
|
||||
self.name = name
|
||||
self.steps = steps or [] # List of Step dicts
|
||||
|
||||
def to_dict(self):
|
||||
"""
|
||||
Return a dictionary representation of the scenario.
|
||||
"""
|
||||
return {'name': self.name, 'steps': self.steps}
|
||||
|
||||
@staticmethod
|
||||
def from_dict(data):
|
||||
"""
|
||||
Create a Scenario object from a dictionary.
|
||||
"""
|
||||
return Scenario(data['name'], data.get('steps', []))
|
||||
|
||||
def get_scenario_dir(self):
|
||||
"""
|
||||
Return the directory path for this scenario's files.
|
||||
"""
|
||||
return os.path.join(CONFIG_DIR, self.name)
|
||||
|
||||
def save(self):
|
||||
"""
|
||||
Save the scenario to disk as a JSON file.
|
||||
"""
|
||||
try:
|
||||
scenario_dir = self.get_scenario_dir()
|
||||
if not os.path.exists(scenario_dir):
|
||||
@@ -65,6 +84,9 @@ class Scenario:
|
||||
|
||||
@staticmethod
|
||||
def load(name):
|
||||
"""
|
||||
Load a scenario from disk by name.
|
||||
"""
|
||||
try:
|
||||
scenario_dir = os.path.join(CONFIG_DIR, name)
|
||||
with open(os.path.join(scenario_dir, 'scenario.json'), 'r') as f:
|
||||
@@ -77,9 +99,16 @@ class Scenario:
|
||||
|
||||
@staticmethod
|
||||
def list_all():
|
||||
"""
|
||||
List all scenario directories.
|
||||
"""
|
||||
return [d for d in os.listdir(CONFIG_DIR) if os.path.isdir(os.path.join(CONFIG_DIR, d))]
|
||||
|
||||
def take_screenshot_with_tkinter():
|
||||
"""
|
||||
Use Tkinter to let the user select a region of the screen for a screenshot.
|
||||
Returns a dict with x, y, width, height.
|
||||
"""
|
||||
root = tk.Tk()
|
||||
root.attributes("-alpha", 0.3)
|
||||
root.attributes("-fullscreen", True)
|
||||
@@ -130,7 +159,15 @@ def take_screenshot_with_tkinter():
|
||||
return selection_rect
|
||||
|
||||
class MainWindow(QtWidgets.QMainWindow):
|
||||
"""
|
||||
Main application window for Scenario Image Automation.
|
||||
Handles UI, scenario management, and automation logic.
|
||||
"""
|
||||
def automation_loop(self):
|
||||
"""
|
||||
Main loop for running automation steps. Continuously looks for image matches
|
||||
in the selected window or screen and performs actions as defined in the scenario.
|
||||
"""
|
||||
logger.info('Automation loop started.')
|
||||
try:
|
||||
while self.running:
|
||||
@@ -206,6 +243,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
self.set_state('Paused')
|
||||
|
||||
def _perform_step_action(self, action, loc, shape):
|
||||
"""
|
||||
Perform a single step action (click, key, scroll, delay) at the given location.
|
||||
"""
|
||||
act_type = action['type']
|
||||
params = action['params']
|
||||
logger.debug(f'Performing step action: {act_type}, params={params}, loc={loc}, shape={shape}')
|
||||
@@ -245,16 +285,31 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
except Exception as e:
|
||||
logger.error(f'Error performing step action {act_type}: {e}')
|
||||
def set_state(self, text):
|
||||
"""
|
||||
Update the state label in the UI.
|
||||
"""
|
||||
self.state_text = text
|
||||
self.state_label.setText(f'State: {text}')
|
||||
|
||||
def toggle_automation(self):
|
||||
"""
|
||||
Toggle the automation state (start/stop), but prevent toggling twice within 5 seconds.
|
||||
"""
|
||||
now = time.time()
|
||||
if now - self.last_toggle_time < 5:
|
||||
logger.info("Start/Stop toggle ignored: pressed too quickly.")
|
||||
QtWidgets.QMessageBox.information(self, "Wait", "Please wait at least 5 seconds before toggling again.")
|
||||
return
|
||||
self.last_toggle_time = now
|
||||
if self.running:
|
||||
self.stop_automation()
|
||||
else:
|
||||
self.start_automation()
|
||||
|
||||
def start_automation(self):
|
||||
"""
|
||||
Start the automation process and worker thread.
|
||||
"""
|
||||
logger.info('Starting automation.')
|
||||
if not self.current_scenario or self.running:
|
||||
logger.warning('Start Automation: No scenario selected or already running.')
|
||||
@@ -268,6 +323,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
self.listener.start()
|
||||
|
||||
def stop_automation(self):
|
||||
"""
|
||||
Stop the automation process and worker thread.
|
||||
"""
|
||||
logger.info('Stopping automation.')
|
||||
self.running = False
|
||||
self.set_state('Paused')
|
||||
@@ -277,6 +335,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
self.listener = None
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Initialize the main window, UI, and load scenarios.
|
||||
"""
|
||||
super().__init__()
|
||||
self.setWindowTitle('Scenario Image Automation')
|
||||
# Make window smaller and more compact
|
||||
@@ -290,10 +351,15 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
self.worker = None
|
||||
self.current_scenario = None
|
||||
self.selected_step_idx = None
|
||||
self.last_toggle_time = 0 # For debounce of start/stop
|
||||
self.init_ui()
|
||||
self.load_scenarios()
|
||||
|
||||
def init_ui(self):
|
||||
"""
|
||||
Initializes the main UI components and layouts.
|
||||
Sets up scenario and window dropdowns, step list, and action buttons.
|
||||
"""
|
||||
# Set compact font and style
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(9)
|
||||
@@ -316,7 +382,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
scenario_group_layout = QtWidgets.QVBoxLayout()
|
||||
self.combo = QtWidgets.QComboBox()
|
||||
self.combo.setMinimumWidth(90)
|
||||
self.combo.currentIndexChanged.connect(self.select_scenario)
|
||||
self.combo.currentIndexChanged.connect(self._log_combo_scenario)
|
||||
scenario_group_layout.addWidget(self.combo)
|
||||
|
||||
# Window selection dropdown
|
||||
@@ -324,21 +390,21 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
self.window_combo.setMinimumWidth(90)
|
||||
scenario_group_layout.addWidget(QtWidgets.QLabel('Target Window:'))
|
||||
scenario_group_layout.addWidget(self.window_combo)
|
||||
self.window_combo.currentIndexChanged.connect(self.save_selected_window)
|
||||
self.window_combo.currentIndexChanged.connect(self._log_combo_window)
|
||||
self.refresh_window_list()
|
||||
# Scenario actions
|
||||
self.btn_new = QtWidgets.QPushButton('New')
|
||||
self.btn_new.setToolTip('Create new scenario')
|
||||
self.btn_new.clicked.connect(self.create_scenario)
|
||||
self.btn_new.clicked.connect(self._log_btn_new)
|
||||
self.btn_import = QtWidgets.QPushButton('Import')
|
||||
self.btn_import.setToolTip('Import scenario')
|
||||
self.btn_import.clicked.connect(self.import_scenario)
|
||||
self.btn_import.clicked.connect(self._log_btn_import)
|
||||
self.btn_export = QtWidgets.QPushButton('Export')
|
||||
self.btn_export.setToolTip('Export scenario')
|
||||
self.btn_export.clicked.connect(self.export_scenario)
|
||||
self.btn_export.clicked.connect(self._log_btn_export)
|
||||
self.btn_rename_scenario = QtWidgets.QPushButton("Rename")
|
||||
self.btn_rename_scenario.setToolTip('Rename scenario')
|
||||
self.btn_rename_scenario.clicked.connect(self.rename_scenario)
|
||||
self.btn_rename_scenario.clicked.connect(self._log_btn_rename_scenario)
|
||||
scenario_btn_group_layout = QtWidgets.QHBoxLayout()
|
||||
scenario_btn_group_layout.setSpacing(4)
|
||||
scenario_btn_group_layout.setContentsMargins(4, 4, 4, 4)
|
||||
@@ -354,21 +420,21 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
steps_group_layout = QtWidgets.QVBoxLayout()
|
||||
self.steps_list = QtWidgets.QListWidget()
|
||||
self.steps_list.setMinimumHeight(120)
|
||||
self.steps_list.currentRowChanged.connect(self.select_step)
|
||||
self.steps_list.currentRowChanged.connect(self._log_steps_list)
|
||||
steps_group_layout.addWidget(self.steps_list)
|
||||
# Step actions
|
||||
self.btn_add_step = QtWidgets.QPushButton('Add')
|
||||
self.btn_add_step.setToolTip('Add step')
|
||||
self.btn_add_step.clicked.connect(self.add_step)
|
||||
self.btn_add_step.clicked.connect(self._log_btn_add_step)
|
||||
self.btn_edit_step = QtWidgets.QPushButton('Edit')
|
||||
self.btn_edit_step.setToolTip('Edit step')
|
||||
self.btn_edit_step.clicked.connect(self.edit_step)
|
||||
self.btn_edit_step.clicked.connect(self._log_btn_edit_step)
|
||||
self.btn_del_step = QtWidgets.QPushButton('Delete')
|
||||
self.btn_del_step.setToolTip('Delete step')
|
||||
self.btn_del_step.clicked.connect(self.delete_step)
|
||||
self.btn_del_step.clicked.connect(self._log_btn_del_step)
|
||||
self.btn_rename_step = QtWidgets.QPushButton("Rename")
|
||||
self.btn_rename_step.setToolTip('Rename step')
|
||||
self.btn_rename_step.clicked.connect(self.rename_step)
|
||||
self.btn_rename_step.clicked.connect(self._log_btn_rename_step)
|
||||
step_btn_group_layout = QtWidgets.QHBoxLayout()
|
||||
step_btn_group_layout.setSpacing(4)
|
||||
step_btn_group_layout.setContentsMargins(4, 4, 4, 4)
|
||||
@@ -382,7 +448,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
# Start/Stop Combined
|
||||
self.btn_start_stop = QtWidgets.QPushButton(f'Start ({self.hotkey.upper()})')
|
||||
self.btn_start_stop.setMinimumWidth(80)
|
||||
self.btn_start_stop.clicked.connect(self.toggle_automation)
|
||||
self.btn_start_stop.clicked.connect(self._log_btn_start_stop)
|
||||
|
||||
# State label
|
||||
self.state_label = QtWidgets.QLabel('State: Paused')
|
||||
@@ -418,10 +484,100 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
central.setLayout(layout)
|
||||
self.setCentralWidget(central)
|
||||
|
||||
def _log_combo_scenario(self, idx):
|
||||
"""
|
||||
Log scenario combo box change and call select_scenario.
|
||||
"""
|
||||
logger.info(f"UI: Scenario combo changed to index {idx} ({self.combo.currentText()})")
|
||||
self.select_scenario()
|
||||
|
||||
def _log_combo_window(self, idx):
|
||||
"""
|
||||
Log window combo box change and call save_selected_window.
|
||||
"""
|
||||
logger.info(f"UI: Window combo changed to index {idx} ({self.window_combo.currentText()})")
|
||||
self.save_selected_window()
|
||||
|
||||
def _log_btn_new(self):
|
||||
"""
|
||||
Log New Scenario button press and call create_scenario.
|
||||
"""
|
||||
logger.info("UI: New Scenario button pressed")
|
||||
self.create_scenario()
|
||||
|
||||
def _log_btn_import(self):
|
||||
"""
|
||||
Log Import Scenario button press and call import_scenario.
|
||||
"""
|
||||
logger.info("UI: Import Scenario button pressed")
|
||||
self.import_scenario()
|
||||
|
||||
def _log_btn_export(self):
|
||||
"""
|
||||
Log Export Scenario button press and call export_scenario.
|
||||
"""
|
||||
logger.info("UI: Export Scenario button pressed")
|
||||
self.export_scenario()
|
||||
|
||||
def _log_btn_rename_scenario(self):
|
||||
"""
|
||||
Log Rename Scenario button press and call rename_scenario.
|
||||
"""
|
||||
logger.info("UI: Rename Scenario button pressed")
|
||||
self.rename_scenario()
|
||||
|
||||
def _log_steps_list(self, idx):
|
||||
"""
|
||||
Log steps list row change and call select_step.
|
||||
"""
|
||||
logger.info(f"UI: Steps list row changed to {idx}")
|
||||
self.select_step(idx)
|
||||
|
||||
def _log_btn_add_step(self):
|
||||
"""
|
||||
Log Add Step button press and call add_step.
|
||||
"""
|
||||
logger.info("UI: Add Step button pressed")
|
||||
self.add_step()
|
||||
|
||||
def _log_btn_edit_step(self):
|
||||
"""
|
||||
Log Edit Step button press and call edit_step.
|
||||
"""
|
||||
logger.info("UI: Edit Step button pressed")
|
||||
self.edit_step()
|
||||
|
||||
def _log_btn_del_step(self):
|
||||
"""
|
||||
Log Delete Step button press and call delete_step.
|
||||
"""
|
||||
logger.info("UI: Delete Step button pressed")
|
||||
self.delete_step()
|
||||
|
||||
def _log_btn_rename_step(self):
|
||||
"""
|
||||
Log Rename Step button press and call rename_step.
|
||||
"""
|
||||
logger.info("UI: Rename Step button pressed")
|
||||
self.rename_step()
|
||||
|
||||
def _log_btn_start_stop(self):
|
||||
"""
|
||||
Log Start/Stop button press and call toggle_automation.
|
||||
"""
|
||||
logger.info("UI: Start/Stop button pressed")
|
||||
self.toggle_automation()
|
||||
|
||||
def select_step(self, idx):
|
||||
"""
|
||||
Set the selected step index in the UI.
|
||||
"""
|
||||
self.selected_step_idx = idx
|
||||
|
||||
def add_step(self):
|
||||
"""
|
||||
Open the dialog to add a new step to the current scenario.
|
||||
"""
|
||||
self.setWindowState(QtCore.Qt.WindowState.WindowMinimized)
|
||||
try:
|
||||
dlg = StepDialog(self)
|
||||
@@ -437,6 +593,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
logger.info(f'Added step: {step}')
|
||||
|
||||
def edit_step(self):
|
||||
"""
|
||||
Open the dialog to edit the selected step in the current scenario.
|
||||
"""
|
||||
idx = self.selected_step_idx
|
||||
if idx is None or idx < 0 or idx >= len(self.current_scenario.steps):
|
||||
return
|
||||
@@ -455,6 +614,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
logger.info(f'Edited step at idx {idx}')
|
||||
|
||||
def delete_step(self):
|
||||
"""
|
||||
Delete the selected step from the current scenario.
|
||||
"""
|
||||
idx = self.selected_step_idx
|
||||
if idx is None or idx < 0 or idx >= len(self.current_scenario.steps):
|
||||
return
|
||||
@@ -464,6 +626,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
logger.info(f'Deleted step at idx {idx}')
|
||||
|
||||
def save_last_scenario(self, name):
|
||||
"""
|
||||
Save the name of the last selected scenario to disk.
|
||||
"""
|
||||
try:
|
||||
with open(os.path.join(CONFIG_DIR, 'last_scenario.txt'), 'w') as f:
|
||||
f.write(name)
|
||||
@@ -471,6 +636,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
logger.error(f"Could not save last scenario: {e}")
|
||||
|
||||
def read_last_scenario(self):
|
||||
"""
|
||||
Read the name of the last selected scenario from disk.
|
||||
"""
|
||||
try:
|
||||
path = os.path.join(CONFIG_DIR, 'last_scenario.txt')
|
||||
if not os.path.exists(path):
|
||||
@@ -482,6 +650,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
return None
|
||||
|
||||
def load_scenarios(self):
|
||||
"""
|
||||
Load all scenarios and populate the scenario combo box.
|
||||
"""
|
||||
logger.debug('Loading scenarios...')
|
||||
self.combo.clear()
|
||||
scenarios = Scenario.list_all()
|
||||
@@ -508,6 +679,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
self.combo.setCurrentIndex(0)
|
||||
|
||||
def select_scenario(self):
|
||||
"""
|
||||
Load the selected scenario and update the UI.
|
||||
"""
|
||||
name = self.combo.currentText()
|
||||
logger.info(f'Scenario selected: {name}')
|
||||
if name:
|
||||
@@ -517,6 +691,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
self.load_selected_window_from_config()
|
||||
|
||||
def create_scenario(self):
|
||||
"""
|
||||
Open a dialog to create a new scenario.
|
||||
"""
|
||||
name, ok = QtWidgets.QInputDialog.getText(self, 'New Scenario', 'Enter scenario name:')
|
||||
if ok and name:
|
||||
logger.info(f'Creating new scenario: {name}')
|
||||
@@ -526,6 +703,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
self.combo.setCurrentText(name)
|
||||
|
||||
def rename_scenario(self):
|
||||
"""
|
||||
Open a dialog to rename the current scenario.
|
||||
"""
|
||||
if not self.current_scenario:
|
||||
return
|
||||
|
||||
@@ -550,6 +730,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
self.combo.setCurrentText(new_name)
|
||||
|
||||
def rename_step(self):
|
||||
"""
|
||||
Open a dialog to rename the selected step.
|
||||
"""
|
||||
if not self.current_scenario or self.selected_step_idx is None:
|
||||
return
|
||||
|
||||
@@ -563,6 +746,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
self.refresh_lists()
|
||||
|
||||
def import_scenario(self):
|
||||
"""
|
||||
Open a file dialog to import a scenario from a zip file.
|
||||
"""
|
||||
path, _ = QtWidgets.QFileDialog.getOpenFileName(self, 'Import Scenario', '', 'ZIP Files (*.zip)')
|
||||
if path:
|
||||
try:
|
||||
@@ -596,6 +782,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
QtWidgets.QMessageBox.critical(self, "Import Error", f"Failed to import scenario.\n{e}")
|
||||
|
||||
def export_scenario(self):
|
||||
"""
|
||||
Open a file dialog to export the current scenario to a zip file.
|
||||
"""
|
||||
if not self.current_scenario:
|
||||
logger.warning('No scenario selected for export.')
|
||||
return
|
||||
@@ -618,6 +807,12 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
|
||||
|
||||
def add_action(self):
|
||||
"""
|
||||
Open the dialog to add a new action to the step.
|
||||
"""
|
||||
"""
|
||||
Open the dialog to add an action to the current scenario.
|
||||
"""
|
||||
logger.debug('Add Action: Opening action dialog.')
|
||||
dlg = ActionDialog(self, self.current_scenario.images)
|
||||
if dlg.exec():
|
||||
@@ -628,6 +823,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
logger.info(f'Action added: {action}')
|
||||
|
||||
def refresh_lists(self):
|
||||
"""
|
||||
Refresh the step list widget with the current scenario's steps.
|
||||
"""
|
||||
logger.debug('Refreshing steps list.')
|
||||
self.steps_list.clear()
|
||||
if not self.current_scenario:
|
||||
@@ -640,6 +838,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
self.steps_list.addItem(f"{name} [{cond}] imgs: {imgs} actions: {acts}")
|
||||
|
||||
def refresh_window_list(self):
|
||||
"""
|
||||
Populate the window selection dropdown with all open windows and 'Entire Screen'.
|
||||
"""
|
||||
self.window_combo.blockSignals(True)
|
||||
self.window_combo.clear()
|
||||
self.window_combo.addItem('Entire Screen')
|
||||
@@ -653,6 +854,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
self.window_combo.blockSignals(False)
|
||||
|
||||
def save_selected_window(self):
|
||||
"""
|
||||
Save the currently selected window to the scenario's config file.
|
||||
"""
|
||||
if self.current_scenario:
|
||||
selected_window = self.window_combo.currentText()
|
||||
scenario_dir = self.current_scenario.get_scenario_dir()
|
||||
@@ -664,6 +868,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
logger.error(f'Failed to save window selection: {e}')
|
||||
|
||||
def load_selected_window_from_config(self):
|
||||
"""
|
||||
Load the selected window from the scenario's config file and update the dropdown.
|
||||
"""
|
||||
if self.current_scenario:
|
||||
scenario_dir = self.current_scenario.get_scenario_dir()
|
||||
config_path = os.path.join(scenario_dir, 'window_config.json')
|
||||
@@ -684,7 +891,14 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
self.window_combo.setCurrentIndex(0)
|
||||
# StepDialog for creating/editing a step
|
||||
class StepDialog(QtWidgets.QDialog):
|
||||
"""
|
||||
Dialog for creating or editing a step in a scenario.
|
||||
Allows editing images, actions, and step properties.
|
||||
"""
|
||||
def __init__(self, parent=None, step=None):
|
||||
"""
|
||||
Initialize the StepDialog for creating or editing a step.
|
||||
"""
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle('Step Editor')
|
||||
self.resize(600, 400)
|
||||
@@ -769,6 +983,9 @@ class StepDialog(QtWidgets.QDialog):
|
||||
self.setLayout(layout)
|
||||
|
||||
def move_action_up(self):
|
||||
"""
|
||||
Move the selected action up in the action list.
|
||||
"""
|
||||
idx = self.act_list.currentRow()
|
||||
if idx > 0:
|
||||
self.actions[idx-1], self.actions[idx] = self.actions[idx], self.actions[idx-1]
|
||||
@@ -777,6 +994,9 @@ class StepDialog(QtWidgets.QDialog):
|
||||
self.act_list.setCurrentRow(idx-1)
|
||||
|
||||
def move_action_down(self):
|
||||
"""
|
||||
Move the selected action down in the action list.
|
||||
"""
|
||||
idx = self.act_list.currentRow()
|
||||
if idx < len(self.actions) - 1 and idx >= 0:
|
||||
self.actions[idx+1], self.actions[idx] = self.actions[idx], self.actions[idx+1]
|
||||
@@ -786,6 +1006,9 @@ class StepDialog(QtWidgets.QDialog):
|
||||
|
||||
class NameImageDialog(QtWidgets.QDialog):
|
||||
def __init__(self, pixmap, parent=None):
|
||||
"""
|
||||
Initialize the dialog for naming a new image.
|
||||
"""
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("Name Your Image")
|
||||
|
||||
@@ -810,10 +1033,16 @@ class StepDialog(QtWidgets.QDialog):
|
||||
layout.addWidget(button_box)
|
||||
|
||||
def get_name(self):
|
||||
"""
|
||||
Return the entered image name.
|
||||
"""
|
||||
return self.name_edit.text()
|
||||
|
||||
|
||||
def add_image_to_step(self):
|
||||
"""
|
||||
Add a new image to the step by taking a screenshot and letting the user select a region.
|
||||
"""
|
||||
main_window = self.parent()
|
||||
step_name = self.name_edit.text()
|
||||
if not step_name:
|
||||
@@ -888,6 +1117,9 @@ class StepDialog(QtWidgets.QDialog):
|
||||
QtWidgets.QMessageBox.critical(self, 'Error', 'Failed to save the screenshot file.')
|
||||
|
||||
def update_image_preview(self, current, previous):
|
||||
"""
|
||||
Update the image preview label when a new image is selected.
|
||||
"""
|
||||
if current:
|
||||
idx = self.img_list.row(current)
|
||||
path = self.images[idx]['path']
|
||||
@@ -897,6 +1129,9 @@ class StepDialog(QtWidgets.QDialog):
|
||||
self.img_preview.setText('Image Preview')
|
||||
|
||||
def update_sensitivity_spin(self, idx):
|
||||
"""
|
||||
Update the sensitivity spinbox for the selected image.
|
||||
"""
|
||||
if idx < 0 or idx >= len(self.images):
|
||||
self.sensitivity_spin.setValue(0.9)
|
||||
self.sensitivity_spin.setEnabled(False)
|
||||
@@ -906,18 +1141,27 @@ class StepDialog(QtWidgets.QDialog):
|
||||
self.sensitivity_spin.setValue(img.get('sensitivity', 0.9))
|
||||
|
||||
def save_sensitivity_for_image(self, value):
|
||||
"""
|
||||
Save the sensitivity value for the currently selected image.
|
||||
"""
|
||||
idx = self.img_list.currentRow()
|
||||
if idx < 0 or idx >= len(self.images):
|
||||
return
|
||||
self.images[idx]['sensitivity'] = value
|
||||
|
||||
def delete_image(self):
|
||||
"""
|
||||
Delete the currently selected image from the step.
|
||||
"""
|
||||
idx = self.img_list.currentRow()
|
||||
if idx >= 0:
|
||||
self.images.pop(idx)
|
||||
self.img_list.takeItem(idx)
|
||||
|
||||
def rename_image(self):
|
||||
"""
|
||||
Open a dialog to rename the currently selected image.
|
||||
"""
|
||||
idx = self.img_list.currentRow()
|
||||
if idx < 0:
|
||||
return
|
||||
@@ -931,6 +1175,9 @@ class StepDialog(QtWidgets.QDialog):
|
||||
self.img_list.item(idx).setText(new_name)
|
||||
|
||||
def retake_screenshot(self):
|
||||
"""
|
||||
Retake the screenshot for the currently selected image.
|
||||
"""
|
||||
idx = self.img_list.currentRow()
|
||||
if idx < 0:
|
||||
return
|
||||
@@ -977,12 +1224,18 @@ class StepDialog(QtWidgets.QDialog):
|
||||
self.act_list.addItem(act.get('type', 'action'))
|
||||
|
||||
def delete_action(self):
|
||||
"""
|
||||
Delete the currently selected action from the step.
|
||||
"""
|
||||
idx = self.act_list.currentRow()
|
||||
if idx >= 0:
|
||||
self.actions.pop(idx)
|
||||
self.act_list.takeItem(idx)
|
||||
|
||||
def get_step(self):
|
||||
"""
|
||||
Return the step as a dictionary for saving.
|
||||
"""
|
||||
return {
|
||||
'name': self.name_edit.text(),
|
||||
'condition': self.cond_combo.currentText(),
|
||||
@@ -992,7 +1245,14 @@ class StepDialog(QtWidgets.QDialog):
|
||||
|
||||
# StepActionDialog for adding actions to a step
|
||||
class StepActionDialog(QtWidgets.QDialog):
|
||||
"""
|
||||
Dialog for adding or editing an action for a step.
|
||||
Supports click, key, scroll, and delay actions.
|
||||
"""
|
||||
def __init__(self, parent=None):
|
||||
"""
|
||||
Initialize the StepActionDialog for adding or editing a step action.
|
||||
"""
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle('Add Action')
|
||||
self.type_combo = QtWidgets.QComboBox()
|
||||
@@ -1074,6 +1334,9 @@ class StepActionDialog(QtWidgets.QDialog):
|
||||
self.update_fields()
|
||||
|
||||
def update_fields(self):
|
||||
"""
|
||||
Update the visibility of fields based on the selected action type.
|
||||
"""
|
||||
t = self.type_combo.currentText()
|
||||
self.click_group.setVisible(t == 'click')
|
||||
self.scroll_group.setVisible(t == 'scroll')
|
||||
@@ -1102,6 +1365,9 @@ class StepActionDialog(QtWidgets.QDialog):
|
||||
self.click_group.setVisible(False)
|
||||
|
||||
def pick_absolute_position(self):
|
||||
"""
|
||||
Open an overlay to pick an absolute position on the screen.
|
||||
"""
|
||||
# Overlay to pick a position on screen
|
||||
class PosOverlay(QtWidgets.QWidget):
|
||||
pos_picked = QtCore.pyqtSignal(int, int)
|
||||
@@ -1121,10 +1387,16 @@ class StepActionDialog(QtWidgets.QDialog):
|
||||
overlay.showFullScreen()
|
||||
|
||||
def set_absolute_position(self, x, y):
|
||||
"""
|
||||
Set the absolute position fields to the picked coordinates.
|
||||
"""
|
||||
self.abs_x_spin.setValue(x)
|
||||
self.abs_y_spin.setValue(y)
|
||||
|
||||
def capture_key(self):
|
||||
"""
|
||||
Open a dialog to capture a key press from the user.
|
||||
"""
|
||||
# Open a modal dialog to capture a key press
|
||||
dlg = QtWidgets.QDialog(self)
|
||||
dlg.setWindowTitle('Press a key')
|
||||
@@ -1155,6 +1427,9 @@ class StepActionDialog(QtWidgets.QDialog):
|
||||
dlg.exec()
|
||||
|
||||
def get_action(self):
|
||||
"""
|
||||
Return the action as a dictionary for saving.
|
||||
"""
|
||||
t = self.type_combo.currentText()
|
||||
params = {}
|
||||
if t == 'click':
|
||||
@@ -1178,6 +1453,9 @@ class StepActionDialog(QtWidgets.QDialog):
|
||||
|
||||
|
||||
def perform_step_action(self, action, loc, shape):
|
||||
"""
|
||||
Perform the given step action at the specified location.
|
||||
"""
|
||||
act_type = action['type']
|
||||
params = action['params']
|
||||
logger.debug(f'Performing step action: {act_type}, params={params}, loc={loc}, shape={shape}')
|
||||
@@ -1218,7 +1496,13 @@ class StepActionDialog(QtWidgets.QDialog):
|
||||
logger.error(f'Error performing step action {act_type}: {e}')
|
||||
|
||||
class ActionDialog(QtWidgets.QDialog):
|
||||
"""
|
||||
Dialog for adding a generic action to a scenario (legacy/unused in main flow).
|
||||
"""
|
||||
def __init__(self, parent=None, images=None):
|
||||
"""
|
||||
Initialize the ActionDialog for adding a generic action.
|
||||
"""
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle('Add Action')
|
||||
self.type_combo = QtWidgets.QComboBox()
|
||||
@@ -1253,6 +1537,9 @@ class ActionDialog(QtWidgets.QDialog):
|
||||
self.setLayout(layout)
|
||||
|
||||
def get_action(self):
|
||||
"""
|
||||
Return the action as a dictionary for saving.
|
||||
"""
|
||||
try:
|
||||
params = json.loads(self.param_edit.text())
|
||||
except Exception:
|
||||
|
||||
Reference in New Issue
Block a user