From 0b267a676dda9ea53d252e27a4ad68bcf6f40d4a Mon Sep 17 00:00:00 2001 From: Avasam Date: Tue, 14 Dec 2021 22:58:19 -0500 Subject: [PATCH 1/7] hooked new settings window to actual values --- .flake8 | 2 +- README.md | 1 - pyproject.toml | 5 + res/design.ui | 61 ++++++--- res/settings.ui | 135 ++++++------------- scripts/compile_resources.bat | 1 + scripts/designer.bat | 7 +- src/AutoSplit.py | 243 +++++++++++++++------------------- src/AutoSplitImage.py | 6 +- src/capture_windows.py | 25 ++-- src/error_messages.py | 10 +- src/hotkeys.py | 88 +++++++----- src/menu_bar.py | 113 +++++++++++++++- src/screen_region.py | 28 ++-- src/settings_file.py | 86 +++++++----- src/split_parser.py | 8 +- 16 files changed, 452 insertions(+), 367 deletions(-) diff --git a/.flake8 b/.flake8 index 65910611..2715e740 100644 --- a/.flake8 +++ b/.flake8 @@ -12,7 +12,7 @@ per-file-ignores= __init__.pyi:Q000 ; PyQt methods ignore-names=closeEvent,paintEvent,keyPressEvent,mousePressEvent,mouseMoveEvent,mouseReleaseEvent -; McCabe max-complexity is also taken care of by Pylint and doesn't fail teh build there +; McCabe max-complexity is also taken care of by Pylint and doesn't fail the build there ; So this is the hard limit max-complexity=32 inline-quotes=" diff --git a/README.md b/README.md index 2e43e50d..4084e5e5 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,6 @@ This program can be used to automatically start, split, and reset your preferred - {d} dummy split image. When matched, it moves to the next image without hitting your split hotkey. - {b} split when similarity goes below the threshold rather than above. When a split image filename has this flag, the split image similarity will go above the threshold, do nothing, and then split the next time the similarity goes below the threshold. - {p} pause flag. When a split image filename has this flag, it will hit your pause hotkey rather than your split hokey. - - A pause flag and a dummy flag `{pd}` cannot be used together - Filename examples: - `001_SplitName_(0.9)_[10].png` is a split image with a threshold of 0.9 and a pause time of 10 seconds. - `002_SplitName_(0.9)_[10]_{d}.png` is the second split image with a threshold of 0.9, pause time of 10, and is a dummy split. diff --git a/pyproject.toml b/pyproject.toml index 8001ad5f..3f71a85f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,6 +15,11 @@ aggressive = 3 [tool.pyright] pythonPlatform = "Windows" typeCheckingMode = "strict" +# Extra strict +reportPropertyTypeMismatch=true +reportUninitializedInstanceVariable=true +reportCallInDefaultInitializer=true +reportImplicitStringConcatenation=true ignore = [ # Auto generated "src/gen/", diff --git a/res/design.ui b/res/design.ui index 80334881..ea0037ef 100644 --- a/res/design.ui +++ b/res/design.ui @@ -1,7 +1,7 @@ - main_window - + MainWindow + 0 @@ -37,7 +37,7 @@ AutoSplit - + :/resources/icon.ico:/resources/icon.ico @@ -93,6 +93,9 @@ + + false + 650 @@ -108,7 +111,10 @@ Reset - + + + false + 650 @@ -124,7 +130,10 @@ Undo - + + + false + 712 @@ -196,6 +205,9 @@ + + Qt::AlignCenter + @@ -212,6 +224,9 @@ + + Qt::AlignCenter + @@ -265,7 +280,7 @@ - 9999 + @@ -332,7 +347,7 @@ - Image Filename + - Qt::AlignCenter @@ -507,7 +522,7 @@ - Window Name + - Qt::AlignCenter @@ -516,8 +531,8 @@ - 450 - 309 + 451 + 313 67 20 @@ -796,8 +811,8 @@ - 450 - 345 + 449 + 344 98 16 @@ -809,8 +824,8 @@ - 551 - 345 + 550 + 344 98 16 @@ -822,17 +837,20 @@ - 519 - 309 + 520 + 313 131 20 - x/x + N/A + + false + 449 @@ -852,6 +870,9 @@ + + false + 744 @@ -874,8 +895,8 @@ select_region_button start_auto_splitter_button reset_button - undo_button - skip_button + undo_split_button + skip_split_button check_fps_button fps_label current_image_label @@ -1027,7 +1048,7 @@ height_spinbox - + diff --git a/res/settings.ui b/res/settings.ui index 22aa4a35..a388d7d6 100644 --- a/res/settings.ui +++ b/res/settings.ui @@ -1,13 +1,13 @@ - dialog_settings - + DialogSettings + 0 0 289 - 570 + 540 @@ -19,13 +19,13 @@ 289 - 570 + 540 289 - 570 + 540 @@ -40,19 +40,6 @@ false - - - - 127 - 538 - 156 - 24 - - - - QDialogButtonBox::Cancel|QDialogButtonBox::Save - - @@ -93,7 +80,7 @@ false - + 138 @@ -102,23 +89,17 @@ 22 - - - - - 0 + + ArrowCursor - 30.000000000000000 + 20 - 5000.000000000000000 - - - 1.000000000000000 + 240 - 60.000000000000000 + 60 @@ -244,7 +225,7 @@ Default Pause Time (sec): - + 167 @@ -288,7 +269,7 @@ Default Similarity Threshold: - + 167 @@ -332,7 +313,7 @@ false - + 6 @@ -351,10 +332,10 @@ teset - <html><head/><body><p>Custom image settings and flags are set in the <br></br> image file name. These will override the default <br></br> values. View the <a href="https://github.com/Toufool/Auto-Split#readme"><span style=" text-decoration: underline; color:#0000ff;">README</span></a> for full details on all <br></br> available custom image settings</p></body></html> + <html><head/><body><p>Custom image settings and flags are set in the <br></br> image file name. These will override the default <br></br> values. View the <a href="https://github.com/Toufool/Auto-Split#readme"><span style=" text-decoration: underline; color:#0000ff;">README</span></a> for full details on all <br></br> available custom image settings.</p></body></html> - + 6 @@ -373,7 +354,7 @@ Default Delay Time (ms): - + 167 @@ -382,26 +363,14 @@ 22 + + ArrowCursor + After an image is matched, this is the amount of time in millseconds that will be delayed before splitting. - - - - - 0 - - - 0.000000000000000 - - 36000000.000000000000000 - - - 1.000000000000000 - - - 0.000000000000000 + 999999999 @@ -430,7 +399,7 @@ 180 - 133 + 130 81 21 @@ -443,10 +412,13 @@ + + true + 76 - 28 + 30 94 20 @@ -481,7 +453,7 @@ 6 - 31 + 32 71 16 @@ -494,7 +466,7 @@ 76 - 53 + 55 94 20 @@ -532,7 +504,7 @@ 6 - 55 + 57 41 16 @@ -545,7 +517,7 @@ 180 - 53 + 55 81 21 @@ -590,7 +562,7 @@ 76 - 133 + 130 94 20 @@ -622,7 +594,7 @@ 180 - 106 + 105 81 21 @@ -638,7 +610,7 @@ 6 - 108 + 107 61 16 @@ -651,7 +623,7 @@ 76 - 106 + 105 94 20 @@ -672,46 +644,13 @@ skip_split_input pause_input default_comparison_method - default_similarity_threshold_double_spinbox - default_pause_time_double_spinbox + default_similarity_threshold_spinbox + default_pause_time_spinbox loop_splits_checkbox fps_limit_spinbox live_capture_region_checkbox force_print_window_checkbox - - - save_cancel_dialog_button_box - accepted() - dialog_settings - accept() - - - 194 - 541 - - - 140 - 282 - - - - - save_cancel_dialog_button_box - rejected() - dialog_settings - reject() - - - 194 - 541 - - - 140 - 282 - - - - + diff --git a/scripts/compile_resources.bat b/scripts/compile_resources.bat index 3c1398ff..d5ef6cf0 100644 --- a/scripts/compile_resources.bat +++ b/scripts/compile_resources.bat @@ -2,5 +2,6 @@ cd "%~dp0.." md .\src\gen pyuic6 ".\res\about.ui" -o ".\src\gen\about.py" pyuic6 ".\res\design.ui" -o ".\src\gen\design.py" +pyuic6 ".\res\settings.ui" -o ".\src\gen\settings.py" pyuic6 ".\res\update_checker.ui" -o ".\src\gen\update_checker.py" pyside6-rcc ".\res\resources.qrc" -o ".\src\gen\resources_rc.py" diff --git a/scripts/designer.bat b/scripts/designer.bat index 3b7af65e..d3b61064 100644 --- a/scripts/designer.bat +++ b/scripts/designer.bat @@ -11,4 +11,9 @@ IF NOT DEFINED PYTHONPATH ( SET PYTHONPATH=!pythonFiles[0]! ) -START "Qt Designer" "%PYTHONPATH:~0,-11%\Lib\site-packages\qt6_applications\Qt\bin\designer.exe" "%~d0%~p0..\res\design.ui" "%~d0%~p0..\res\about.ui" "%~d0%~p0..\res\update_checker.ui" +START "Qt Designer" "%PYTHONPATH:~0,-11%\Lib\site-packages\qt6_applications\Qt\bin\designer.exe"^ + "%~d0%~p0..\res\design.ui"^ + "%~d0%~p0..\res\about.ui"^ + "%~d0%~p0..\res\settings.ui"^ + "%~d0%~p0..\res\update_checker.ui" + diff --git a/src/AutoSplit.py b/src/AutoSplit.py index 97031748..64f73d2b 100644 --- a/src/AutoSplit.py +++ b/src/AutoSplit.py @@ -28,18 +28,18 @@ import error_messages import settings_file as settings from AutoControlledWorker import AutoControlledWorker -from capture_windows import capture_region, Rect, set_ui_image -from gen import about, design, update_checker -from hotkeys import send_command, after_setting_hotkey, set_split_hotkey, set_reset_hotkey, set_skip_split_hotkey, \ - set_undo_split_hotkey, set_pause_hotkey -from menu_bar import open_about, VERSION, view_help, check_for_updates, open_update_checker +from capture_windows import capture_region, set_ui_image +from gen import about, design, settings as settings_ui, update_checker +from hotkeys import send_command, after_setting_hotkey +from menu_bar import get_default_settings_from_ui, open_about, VERSION, open_settings, view_help, check_for_updates, \ + open_update_checker from screen_region import select_region, select_window, align_region, validate_before_parsing from settings_file import FROZEN from split_parser import BELOW_FLAG, DUMMY_FLAG, PAUSE_FLAG, parse_and_validate_images -CREATE_NEW_ISSUE_MESSAGE = "Please create a New Issue at " \ - "github.com/Toufool/Auto-Split/issues, describe what happened, and copy & paste the error message below" -START_IMAGE_TEXT = "Start Image" +CREATE_NEW_ISSUE_MESSAGE = ( + "Please create a New Issue at " + + "github.com/Toufool/Auto-Split/issues, describe what happened, and copy & paste the error message below") START_AUTO_SPLITTER_TEXT = "Start Auto Splitter" CHECK_FPS_ITERATIONS = 10 @@ -47,14 +47,14 @@ os.environ["REQUESTS_CA_BUNDLE"] = certifi.where() -def make_excepthook(main_window: AutoSplit): +def make_excepthook(autosplit: AutoSplit): def excepthook(exception_type: type[BaseException], exception: BaseException, _traceback: Optional[TracebackType]): # Catch Keyboard Interrupts for a clean close if exception_type is KeyboardInterrupt or isinstance(exception, KeyboardInterrupt): sys.exit(0) - main_window.show_error_signal.emit(lambda: error_messages.exception_traceback( + autosplit.show_error_signal.emit(lambda: error_messages.exception_traceback( "AutoSplit encountered an unhandled exception and will try to recover, " - f"however, there is no guarantee everything will work properly. {CREATE_NEW_ISSUE_MESSAGE}", + + f"however, there is no guarantee everything will work properly. {CREATE_NEW_ISSUE_MESSAGE}", exception)) return excepthook @@ -82,9 +82,10 @@ class AutoSplit(QMainWindow, design.Ui_MainWindow): timer_start_image = QtCore.QTimer() # Widgets - AboutWidget: about.Ui_AboutAutoSplitWidget - UpdateCheckerWidget: update_checker.Ui_UpdateChecker - CheckForUpdatesThread: QtCore.QThread + AboutWidget: Optional[about.Ui_AboutAutoSplitWidget] = None + UpdateCheckerWidget: Optional[update_checker.Ui_UpdateChecker] = None + CheckForUpdatesThread: Optional[QtCore.QThread] = None + SettingsWidget: Optional[settings_ui.Ui_DialogSettings] = None # hotkeys need to be initialized to be passed as thread arguments in hotkeys.py # and for type safety in both hotkeys.py and settings_file.py @@ -95,12 +96,10 @@ class AutoSplit(QMainWindow, design.Ui_MainWindow): pause_hotkey: Optional[Callable[[], None]] = None # Initialize a few attributes - split_image_directory = "" hwnd = 0 """Window Handle used for Capture Region""" - window_text = "" - selection = Rect() last_saved_settings: list[Union[str, float, int, bool]] = [] + similarity = 0.0 live_image_function_on_open = True split_image_number = 0 split_images_and_loop_number: list[tuple[AutoSplitImage, int]] = [] @@ -130,34 +129,32 @@ def __init__(self, parent: Optional[QWidget] = None): # Setup global error handling self.show_error_signal.connect(lambda errorMessageBox: errorMessageBox()) - # Whithin LiveSplit excepthook needs to use main_window's signals to show errors + # Whithin LiveSplit excepthook needs to use MainWindow's signals to show errors sys.excepthook = make_excepthook(self) self.setupUi(self) + # Get default values defined in SettingsDialog + self.settings_dict = get_default_settings_from_ui(self) settings.load_check_for_updates_on_open(self) - # close all processes when closing window self.action_view_help.triggered.connect(view_help) self.action_about.triggered.connect(lambda: open_about(self)) self.action_check_for_updates.triggered.connect(lambda: check_for_updates(self)) - self.action_save_settings.triggered.connect(lambda: settings.save_settings(self)) - self.action_save_settings_as.triggered.connect(lambda: settings.save_settings_as(self)) - self.action_load_settings.triggered.connect(lambda: settings.load_settings(self)) + self.action_settings.triggered.connect(lambda: open_settings(self)) + self.action_save_profile.triggered.connect(lambda: settings.save_settings(self)) + self.action_save_profile_as.triggered.connect(lambda: settings.save_settings_as(self)) + self.action_load_profile.triggered.connect(lambda: settings.load_settings(self)) + + if self.SettingsWidget: + self.SettingsWidget.split_input.setEnabled(False) + self.SettingsWidget.reset_input.setEnabled(False) + self.SettingsWidget.skip_split_input.setEnabled(False) + self.SettingsWidget.undo_split_input.setEnabled(False) + self.SettingsWidget.pause_input.setEnabled(False) if self.is_auto_controlled: - self.set_split_hotkey_button.setEnabled(False) - self.set_reset_hotkey_button.setEnabled(False) - self.set_skip_split_hotkey_button.setEnabled(False) - self.set_undo_split_hotkey_button.setEnabled(False) - self.set_pause_hotkey_button.setEnabled(False) self.start_auto_splitter_button.setEnabled(False) - self.split_input.setEnabled(False) - self.reset_input.setEnabled(False) - self.skip_split_input.setEnabled(False) - self.undo_split_input.setEnabled(False) - self.pause_input.setEnabled(False) - self.timer_global_hotkeys_label.setText("Hotkeys Inactive - Use LiveSplit Hotkeys") # Send version and process ID to stdout print(f"{VERSION}\n{os.getpid()}", flush=True) @@ -183,14 +180,9 @@ def __init__(self, parent: Optional[QWidget] = None): self.undo_split_button.clicked.connect(self.__undo_split) self.next_image_button.clicked.connect(lambda: self.__skip_split(True)) self.previous_image_button.clicked.connect(lambda: self.__undo_split(True)) - self.set_split_hotkey_button.clicked.connect(lambda: set_split_hotkey(self)) - self.set_reset_hotkey_button.clicked.connect(lambda: set_reset_hotkey(self)) - self.set_skip_split_hotkey_button.clicked.connect(lambda: set_skip_split_hotkey(self)) - self.set_undo_split_hotkey_button.clicked.connect(lambda: set_undo_split_hotkey(self)) - self.set_pause_hotkey_button.clicked.connect(lambda: set_pause_hotkey(self)) self.align_region_button.clicked.connect(lambda: align_region(self)) self.select_window_button.clicked.connect(lambda: select_window(self)) - self.start_image_reload_button.clicked.connect(lambda: self.load_start_image(True, True)) + self.reload_start_image_button.clicked.connect(lambda: self.load_start_image(True, True)) self.action_check_for_updates_on_open.changed.connect(lambda: settings.set_check_for_updates_on_open( self, self.action_check_for_updates_on_open.isChecked()) @@ -213,7 +205,7 @@ def __init__(self, parent: Optional[QWidget] = None): self.pause_signal.connect(self.pause) # live image checkbox - self.live_image_checkbox.clicked.connect(self.check_live_image) + self.timer_live_image.start(int(1000 / 60)) self.timer_live_image.timeout.connect(self.__live_image_function) # Automatic timer start @@ -236,26 +228,19 @@ def __browse(self): new_split_image_directory = QFileDialog.getExistingDirectory( self, "Select Split Image Directory", - os.path.join(self.split_image_directory or settings.auto_split_directory, "..")) + os.path.join(self.settings_dict["split_image_directory"] or settings.auto_split_directory, "..")) # If the user doesn't select a folder, it defaults to "". if new_split_image_directory: # set the split image folder line to the directory text - self.split_image_directory = new_split_image_directory + self.settings_dict["split_image_directory"] = new_split_image_directory self.split_image_folder_input.setText(f"{new_split_image_directory}/") self.load_start_image() - def check_live_image(self): - if self.live_image_checkbox.isChecked(): - self.timer_live_image.start(int(1000 / 60)) - else: - self.timer_live_image.stop() - self.__live_image_function() - def __live_image_function(self): try: - self.capture_region_window_label.setText(self.window_text) - if not self.window_text: + self.capture_region_window_label.setText(self.settings_dict["captured_window_title"]) + if not self.settings_dict["captured_window_title"]: self.timer_live_image.stop() self.live_image.clear() if self.live_image_function_on_open: @@ -263,7 +248,8 @@ def __live_image_function(self): return # Set live image in UI if self.hwnd: - capture = capture_region(self.hwnd, self.selection, self.force_print_window_checkbox.isChecked()) + capture = capture_region(self.hwnd, self.settings_dict["capture_region"], + self.settings_dict["force_print_window"]) set_ui_image(self.live_image, capture, False) except AttributeError: @@ -271,14 +257,14 @@ def __live_image_function(self): def load_start_image(self, started_by_button: bool = False, wait_for_delay: bool = True): self.timer_start_image.stop() - self.current_split_image_file_label.setText(" ") - self.start_image_label.setText(f"{START_IMAGE_TEXT}: not found") + self.current_image_file_label.setText("-") + self.start_image_status_value_label.setText("not found") QApplication.processEvents() if not self.is_auto_controlled \ - and (not self.split_input.text() - or not self.reset_input.text() - or not self.pause_input.text()): + and (not self.settings_dict["split_hotkey"] + or not self.settings_dict["reset_hotkey"] + or not self.settings_dict["pause_hotkey"]): error_messages.load_start_image() return @@ -295,17 +281,17 @@ def load_start_image(self, started_by_button: bool = False, wait_for_delay: bool start_pause_time = self.start_image.get_pause_time(self) if not wait_for_delay and start_pause_time > 0: self.check_start_image_timestamp = time() + start_pause_time - self.start_image_label.setText(f"{START_IMAGE_TEXT}: paused") - self.highest_similarity_label.setText(" ") - self.current_similarity_threshold_number_label.setText(" ") + self.start_image_status_value_label.setText("paused") + self.table_current_image_highest_label.setText("-") + self.table_current_image_threshold_label.setText("-") else: self.check_start_image_timestamp = 0.0 - self.start_image_label.setText(f"{START_IMAGE_TEXT}: ready") + self.start_image_status_value_label.setText("ready") self.__update_split_image(self.start_image) self.highest_similarity = 0.0 self.start_image_split_below_threshold = False - self.timer_start_image.start(int(1000 / self.fps_limit_spinbox.value())) + self.timer_start_image.start(int(1000 / self.settings_dict["fps_limit"])) QApplication.processEvents() @@ -313,35 +299,31 @@ def __start_image_function(self): if self.start_image is None \ or not self.start_image \ or time() < self.check_start_image_timestamp \ - or (not self.split_input.text() and not self.is_auto_controlled): + or (not self.settings_dict["split_hotkey"] and not self.is_auto_controlled): pause_time_left = f"{self.check_start_image_timestamp - time():.1f}" self.current_split_image.setText( - f"None\n (Paused before loading {START_IMAGE_TEXT}).\n {pause_time_left} sec remaining") + f"None\n (Paused before loading Start Image).\n {pause_time_left} sec remaining") return if self.check_start_image_timestamp > 0: self.check_start_image_timestamp = 0.0 - self.start_image_label.setText(f"{START_IMAGE_TEXT}: ready") + self.start_image_status_value_label.setText("ready") self.__update_split_image(self.start_image) capture = self.__get_capture_for_comparison() start_image_threshold = self.start_image.get_similarity_threshold(self) start_image_similarity = self.start_image.compare_with_capture(self, capture) - self.current_similarity_threshold_number_label.setText(f"{start_image_threshold:.2f}") + self.table_current_image_threshold_label.setText(f"{start_image_threshold:.2f}") # Show live similarity if the checkbox is checked - self.live_similarity_label.setText(str(start_image_similarity)[:4] - if self.show_live_similarity_checkbox.isChecked() - else " ") + self.table_current_image_live_label.setText(str(start_image_similarity)[:4]) # If the similarity becomes higher than highest similarity, set it as such. if start_image_similarity > self.highest_similarity: self.highest_similarity = start_image_similarity # Show live highest similarity if the checkbox is checked - self.highest_similarity_label.setText(str(self.highest_similarity)[:4] - if self.show_highest_similarity_checkbox.isChecked() - else " ") + self.table_current_image_highest_label.setText(str(self.highest_similarity)[:4]) # If the {b} flag is set, let similarity go above threshold first, then split on similarity below threshold # Otherwise just split when similarity goes above threshold @@ -361,7 +343,7 @@ def __start_image_function(self): # delay start image if needed if self.start_image.delay > 0: - self.start_image_label.setText(f"{START_IMAGE_TEXT}: delaying start...") + self.start_image_status_value_label.setText("delaying start...") delay_start_time = time() start_delay = self.start_image.delay / 1000 while time() - delay_start_time < start_delay: @@ -371,36 +353,24 @@ def __start_image_function(self): # Email sent to pyqt@riverbankcomputing.com QtTest.QTest.qWait(1) # type: ignore - self.start_image_label.setText(f"{START_IMAGE_TEXT}: started") + self.start_image_status_value_label.setText("started") send_command(self, "start") # Email sent to pyqt@riverbankcomputing.com - QtTest.QTest.qWait(int(1 / self.fps_limit_spinbox.value())) # type: ignore + QtTest.QTest.qWait(int(1 / self.settings_dict["fps_limit"])) # type: ignore self.start_auto_splitter() # update x, y, width, height when spinbox values are changed def __update_x(self): - try: - self.selection.left = self.x_spinbox.value() - self.selection.right = self.selection.left + self.width_spinbox.value() - self.check_live_image() - except AttributeError: - pass + self.settings_dict["capture_region"].x = self.x_spinbox.value() def __update_y(self): - try: - self.selection.top = self.y_spinbox.value() - self.selection.bottom = self.selection.top + self.height_spinbox.value() - self.check_live_image() - except AttributeError: - pass + self.settings_dict["capture_region"].y = self.y_spinbox.value() def __update_width(self): - self.selection.right = self.selection.left + self.width_spinbox.value() - self.check_live_image() + self.settings_dict["capture_region"].width = self.width_spinbox.value() def __update_height(self): - self.selection.bottom = self.selection.top + self.height_spinbox.value() - self.check_live_image() + self.settings_dict["capture_region"].height = self.height_spinbox.value() def __take_screenshot(self): if not validate_before_parsing(self, check_empty_directory=False): @@ -411,13 +381,17 @@ def __take_screenshot(self): # which is a problem, but I doubt anyone will get to 1000 split images... screenshot_index = 1 while True: - screenshot_path = os.path.join(self.split_image_directory, f"{screenshot_index:03}_SplitImage.png") + screenshot_path = os.path.join( + self.settings_dict["split_image_directory"], + f"{screenshot_index:03}_SplitImage.png") if not os.path.exists(screenshot_path): break screenshot_index += 1 # Grab screenshot of capture region - capture = capture_region(self.hwnd, self.selection, self.force_print_window_checkbox.isChecked()) + capture = capture_region(self.hwnd, + self.settings_dict["capture_region"], + self.settings_dict["force_print_window"]) if capture is None: error_messages.region() return @@ -427,7 +401,7 @@ def __take_screenshot(self): os.startfile(screenshot_path) def __check_fps(self): - self.fps_value_label.setText(" ") + self.fps_value_label.clear() if not (validate_before_parsing(self) and parse_and_validate_images(self)): return @@ -521,15 +495,15 @@ def start_auto_splitter(self): or (not self.start_auto_splitter_button.isEnabled() and not self.is_auto_controlled): return - start_label: str = self.start_image_label.text() + start_label: str = self.start_image_status_value_label.text() if start_label.endswith("ready") or start_label.endswith("paused"): - self.start_image_label.setText(f"{START_IMAGE_TEXT}: not ready") + self.start_image_status_value_label.setText("not ready") self.start_auto_splitter_signal.emit() def __check_for_reset(self): if self.start_auto_splitter_button.text() == START_AUTO_SPLITTER_TEXT: - if self.auto_start_on_reset_checkbox.isChecked(): + if self.settings_dict["loop_splits"]: self.start_auto_splitter_signal.emit() else: self.gui_changes_on_reset() @@ -537,7 +511,7 @@ def __check_for_reset(self): return False def __auto_splitter(self): - if not self.split_input.text() and not self.is_auto_controlled: + if not self.settings_dict["split_hotkey"] and not self.is_auto_controlled: self.gui_changes_on_reset() error_messages.split_hotkey() return @@ -606,20 +580,14 @@ def __auto_splitter(self): self.similarity = self.split_image.compare_with_capture(self, capture) # show live similarity if the checkbox is checked - self.live_similarity_label.setText( - str(self.similarity)[:4] - if self.show_live_similarity_checkbox.isChecked() - else " ") + self.table_current_image_live_label.setText(str(self.similarity)[:4]) # if the similarity becomes higher than highest similarity, set it as such. if self.similarity > self.highest_similarity: self.highest_similarity = self.similarity # show live highest similarity if the checkbox is checked - self.highest_similarity_label.setText( - str(self.highest_similarity)[:4] - if self.show_highest_similarity_checkbox.isChecked() - else " ") + self.table_current_image_highest_label.setText(str(self.highest_similarity)[:4]) # If its the last split image and last loop number, disable the next image button # If its the first split image, disable the undo split and previous image buttons @@ -645,7 +613,7 @@ def __auto_splitter(self): break # limit the number of time the comparison runs to reduce cpu usage - frame_interval: float = 1 / self.fps_limit_spinbox.value() + frame_interval: float = 1 / self.settings_dict["fps_limit"] # Email sent to pyqt@riverbankcomputing.com QtTest.QTest.qWait(int(frame_interval - (time() - start) % frame_interval)) # type: ignore QApplication.processEvents() @@ -663,7 +631,7 @@ def __auto_splitter(self): self.waiting_for_split_delay = True self.undo_split_button.setEnabled(False) self.skip_split_button.setEnabled(False) - self.current_split_image_file_label.setText(" ") + self.current_image_file_label.clear() # check for reset while delayed and display a counter of the remaining split delay time delay_start_time = time() @@ -687,7 +655,7 @@ def __auto_splitter(self): # if loop check box is checked and its the last split, go to first split. # else go to the next split image. - if self.loop_checkbox.isChecked() and self.split_image_number == number_of_split_images - 1: + if self.settings_dict["loop_splits"] and self.split_image_number == number_of_split_images - 1: self.split_image_number = 0 else: self.split_image_number += 1 @@ -742,46 +710,49 @@ def gui_changes_on_start(self): self.timer_start_image.stop() self.start_auto_splitter_button.setText("Running...") self.browse_button.setEnabled(False) - self.start_image_reload_button.setEnabled(False) + self.reload_start_image_button.setEnabled(False) self.previous_image_button.setEnabled(True) self.next_image_button.setEnabled(True) + if self.SettingsWidget: + self.SettingsWidget.set_split_hotkey_button.setEnabled(False) + self.SettingsWidget.set_reset_hotkey_button.setEnabled(False) + self.SettingsWidget.set_skip_split_hotkey_button.setEnabled(False) + self.SettingsWidget.set_undo_split_hotkey_button.setEnabled(False) + self.SettingsWidget.set_pause_hotkey_button.setEnabled(False) + if not self.is_auto_controlled: self.start_auto_splitter_button.setEnabled(False) self.reset_button.setEnabled(True) self.undo_split_button.setEnabled(True) self.skip_split_button.setEnabled(True) - self.set_split_hotkey_button.setEnabled(False) - self.set_reset_hotkey_button.setEnabled(False) - self.set_skip_split_hotkey_button.setEnabled(False) - self.set_undo_split_hotkey_button.setEnabled(False) - self.set_pause_hotkey_button.setEnabled(False) QApplication.processEvents() def gui_changes_on_reset(self): self.start_auto_splitter_button.setText(START_AUTO_SPLITTER_TEXT) - self.image_loop_label.setText("Image Loop: -") - self.current_split_image.setText(" ") - self.current_split_image_file_label.setText(" ") - self.live_similarity_label.setText(" ") - self.highest_similarity_label.setText(" ") - self.current_similarity_threshold_number_label.setText(" ") + self.image_loop_value_label.setText("N/A") + self.current_split_image.clear() + self.current_image_file_label.clear() + self.table_current_image_live_label.setText("-") + self.table_current_image_highest_label.setText("-") + self.table_current_image_threshold_label.setText("-") self.browse_button.setEnabled(True) - self.start_image_reload_button.setEnabled(True) + self.reload_start_image_button.setEnabled(True) self.previous_image_button.setEnabled(False) self.next_image_button.setEnabled(False) + if self.SettingsWidget: + self.SettingsWidget.set_split_hotkey_button.setEnabled(True) + self.SettingsWidget.set_reset_hotkey_button.setEnabled(True) + self.SettingsWidget.set_skip_split_hotkey_button.setEnabled(True) + self.SettingsWidget.set_undo_split_hotkey_button.setEnabled(True) + self.SettingsWidget.set_pause_hotkey_button.setEnabled(True) if not self.is_auto_controlled: self.start_auto_splitter_button.setEnabled(True) self.reset_button.setEnabled(False) self.undo_split_button.setEnabled(False) self.skip_split_button.setEnabled(False) - self.set_split_hotkey_button.setEnabled(True) - self.set_reset_hotkey_button.setEnabled(True) - self.set_skip_split_hotkey_button.setEnabled(True) - self.set_undo_split_hotkey_button.setEnabled(True) - self.set_pause_hotkey_button.setEnabled(True) QApplication.processEvents() self.load_start_image(False, False) @@ -790,18 +761,22 @@ def __get_capture_for_comparison(self): """ Grab capture region and resize for comparison """ - capture = capture_region(self.hwnd, self.selection, self.force_print_window_checkbox.isChecked()) + capture = capture_region(self.hwnd, + self.settings_dict["capture_region"], + self.settings_dict["force_print_window"]) # This most likely means we lost capture (ie the captured window was closed, crashed, etc.) if capture is None: # Try to recover by using the window name self.live_image.setText("Trying to recover window...") # https://github.com/kaluluosi/pywin32-stubs/issues/7 - hwnd = win32gui.FindWindow(None, self.window_text) # type: ignore + hwnd = win32gui.FindWindow(None, self.settings_dict["captured_window_title"]) # type: ignore # Don't fallback to desktop if hwnd: self.hwnd = hwnd - capture = capture_region(self.hwnd, self.selection, self.force_print_window_checkbox.isChecked()) + capture = capture_region(self.hwnd, + self.settings_dict["capture_region"], + self.settings_dict["force_print_window"]) return None if capture is None else cv2.resize(capture, COMPARISON_RESIZE, interpolation=cv2.INTER_NEAREST) def __reset_if_should(self, capture: Optional[cv2.ndarray]): @@ -833,15 +808,15 @@ def __update_split_image(self, specific_image: Optional[AutoSplitImage] = None): if self.split_image.bytes is not None: set_ui_image(self.current_split_image, self.split_image.bytes, True) - self.current_split_image_file_label.setText(self.split_image.filename) - self.current_similarity_threshold_number_label.setText(f"{self.split_image.get_similarity_threshold(self):.2f}") + self.current_image_file_label.setText(self.split_image.filename) + self.table_current_image_threshold_label.setText(f"{self.split_image.get_similarity_threshold(self):.2f}") # Set Image Loop # if specific_image and specific_image.image_type == ImageType.START: - self.image_loop_label.setText("Image Loop: N/A") + self.image_loop_value_label.setText("N/A") else: loop_tuple = self.split_images_and_loop_number[self.split_image_number] - self.image_loop_label.setText(f"Image Loop: {loop_tuple[1]}/{loop_tuple[0].loops}") + self.image_loop_value_label.setText(f"{loop_tuple[1]}/{loop_tuple[0].loops}") self.highest_similarity = 0.0 # need to set split below threshold to false each time an image updates. diff --git a/src/AutoSplitImage.py b/src/AutoSplitImage.py index 126f2abe..9eeaf8a9 100644 --- a/src/AutoSplitImage.py +++ b/src/AutoSplitImage.py @@ -46,7 +46,7 @@ def get_pause_time(self, default: Union[AutoSplit, float]): """ default_value: float = default \ if isinstance(default, float) \ - else default.pause_spinbox.value() + else default.settings_dict["default_pause_time"] return default_value if self.__pause_time is None else self.__pause_time def get_similarity_threshold(self, default: Union[AutoSplit, float]): @@ -55,7 +55,7 @@ def get_similarity_threshold(self, default: Union[AutoSplit, float]): """ default_value: float = default \ if isinstance(default, float) \ - else default.similarity_threshold_spinbox.value() + else default.settings_dict["default_similarity_threshold"] return default_value if self.__similarity_threshold is None else self.__similarity_threshold def __init__(self, path: str): @@ -109,7 +109,7 @@ def compare_with_capture( """ comparison_method: int = comparison \ if isinstance(comparison, int) \ - else comparison.comparison_method_combobox.currentIndex() + else comparison.settings_dict["default_comparison_method"] if self.bytes is None or capture is None: return 0.0 diff --git a/src/capture_windows.py b/src/capture_windows.py index c991cc38..c6401efa 100644 --- a/src/capture_windows.py +++ b/src/capture_windows.py @@ -20,17 +20,15 @@ @dataclass -class Rect(ctypes.wintypes.RECT): - """ - Overrides `ctypes.wintypes.RECT` to replace c_long with int for math operators - """ - left: int = -1 # type: ignore - top: int = -1 # type: ignore - right: int = -1 # type: ignore - bottom: int = -1 # type: ignore +class Region(): + def __init__(self, x: int, y: int, width: int, height: int): + self.x = x + self.y = y + self.width = width + self.height = height -def capture_region(hwnd: int, selection: Rect, print_window: bool): +def capture_region(hwnd: int, selection: Region, print_window: bool): """ Captures an image of the region for a window matching the given parameters of the bounding box @@ -40,8 +38,6 @@ def capture_region(hwnd: int, selection: Rect, print_window: bool): @return: The image of the region in the window in BGRA format """ - width: int = selection.right - selection.left - height: int = selection.bottom - selection.top # If the window closes while it's being manipulated, it could cause a crash try: window_dc: int = win32gui.GetWindowDC(hwnd) @@ -54,16 +50,17 @@ def capture_region(hwnd: int, selection: Rect, print_window: bool): compatible_dc = cast(PyCDC, dc_object.CreateCompatibleDC()) bitmap: PyCBitmap = win32ui.CreateBitmap() - bitmap.CreateCompatibleBitmap(dc_object, width, height) + bitmap.CreateCompatibleBitmap(dc_object, selection.width, selection.height) compatible_dc.SelectObject(bitmap) - compatible_dc.BitBlt((0, 0), (width, height), dc_object, (selection.left, selection.top), win32con.SRCCOPY) + compatible_dc.BitBlt((0, 0), (selection.width, selection.height), dc_object, + (selection.x, selection.y), win32con.SRCCOPY) # https://github.com/kaluluosi/pywin32-stubs/issues/5 # pylint: disable=no-member except (win32ui.error, pywintypes.error): # type: ignore return None image = np.frombuffer(cast(bytes, bitmap.GetBitmapBits(True)), dtype="uint8") - image.shape = (height, width, 4) + image.shape = (selection.height, selection.width, 4) try: dc_object.DeleteDC() diff --git a/src/error_messages.py b/src/error_messages.py index 5d8bd382..cb437f36 100644 --- a/src/error_messages.py +++ b/src/error_messages.py @@ -31,12 +31,12 @@ def split_image_directory_empty(): def image_type(image: str): set_text_message(f'"{image}" is not a valid image file, does not exist, ' - "or the full image file path contains a special character.") + + "or the full image file path contains a special character.") def region(): set_text_message("No region is selected or the Capture Region window is not open. " - "Select a region or load settings while the Capture Region window is open.") + + "Select a region or load settings while the Capture Region window is open.") def split_hotkey(): @@ -45,7 +45,7 @@ def split_hotkey(): def pause_hotkey(): set_text_message("Your split image folder contains an image filename with a pause flag {p}, " - "but no pause hotkey is set.") + + "but no pause hotkey is set.") def align_region_image_type(): @@ -82,7 +82,7 @@ def no_settings_file_on_open(): def too_many_settings_files_on_open(): set_text_message("Too many settings files found. " - "Only one can be loaded on open if placed in the same folder as AutoSplit.exe") + + "Only one can be loaded on open if placed in the same folder as AutoSplit.exe") def check_for_updates(): @@ -91,7 +91,7 @@ def check_for_updates(): def load_start_image(): set_text_message("Start Image found, but cannot be loaded unless Start, Reset, and Pause hotkeys are set. " - "Please set these hotkeys, and then click the Reload Start Image button.") + + "Please set these hotkeys, and then click the Reload Start Image button.") def stdin_lost(): diff --git a/src/hotkeys.py b/src/hotkeys.py index 80701127..3a6901e2 100644 --- a/src/hotkeys.py +++ b/src/hotkeys.py @@ -1,6 +1,7 @@ from __future__ import annotations from typing import Literal, Optional, TYPE_CHECKING, Union from collections.abc import Callable + if TYPE_CHECKING: from AutoSplit import AutoSplit @@ -18,27 +19,29 @@ # do all of these after you click "Set Hotkey" but before you type the hotkey. def before_setting_hotkey(autosplit: AutoSplit): autosplit.start_auto_splitter_button.setEnabled(False) - autosplit.set_split_hotkey_button.setEnabled(False) - autosplit.set_reset_hotkey_button.setEnabled(False) - autosplit.set_skip_split_hotkey_button.setEnabled(False) - autosplit.set_undo_split_hotkey_button.setEnabled(False) - autosplit.set_pause_hotkey_button.setEnabled(False) + if autosplit.SettingsWidget: + autosplit.SettingsWidget.set_split_hotkey_button.setEnabled(False) + autosplit.SettingsWidget.set_reset_hotkey_button.setEnabled(False) + autosplit.SettingsWidget.set_skip_split_hotkey_button.setEnabled(False) + autosplit.SettingsWidget.set_undo_split_hotkey_button.setEnabled(False) + autosplit.SettingsWidget.set_pause_hotkey_button.setEnabled(False) # do all of these things after you set a hotkey. a signal connects to this because # changing GUI stuff in the hotkey thread was causing problems def after_setting_hotkey(autosplit: AutoSplit): - autosplit.set_split_hotkey_button.setText(SET_HOTKEY_TEXT) - autosplit.set_reset_hotkey_button.setText(SET_HOTKEY_TEXT) - autosplit.set_skip_split_hotkey_button.setText(SET_HOTKEY_TEXT) - autosplit.set_undo_split_hotkey_button.setText(SET_HOTKEY_TEXT) - autosplit.set_pause_hotkey_button.setText(SET_HOTKEY_TEXT) autosplit.start_auto_splitter_button.setEnabled(True) - autosplit.set_split_hotkey_button.setEnabled(True) - autosplit.set_reset_hotkey_button.setEnabled(True) - autosplit.set_skip_split_hotkey_button.setEnabled(True) - autosplit.set_undo_split_hotkey_button.setEnabled(True) - autosplit.set_pause_hotkey_button.setEnabled(True) + if autosplit.SettingsWidget: + autosplit.SettingsWidget.set_split_hotkey_button.setText(SET_HOTKEY_TEXT) + autosplit.SettingsWidget.set_reset_hotkey_button.setText(SET_HOTKEY_TEXT) + autosplit.SettingsWidget.set_skip_split_hotkey_button.setText(SET_HOTKEY_TEXT) + autosplit.SettingsWidget.set_undo_split_hotkey_button.setText(SET_HOTKEY_TEXT) + autosplit.SettingsWidget.set_pause_hotkey_button.setText(SET_HOTKEY_TEXT) + autosplit.SettingsWidget.set_split_hotkey_button.setEnabled(True) + autosplit.SettingsWidget.set_reset_hotkey_button.setEnabled(True) + autosplit.SettingsWidget.set_skip_split_hotkey_button.setEnabled(True) + autosplit.SettingsWidget.set_undo_split_hotkey_button.setEnabled(True) + autosplit.SettingsWidget.set_pause_hotkey_button.setEnabled(True) def is_digit(key: Optional[str]): @@ -57,15 +60,15 @@ def send_command(autosplit: AutoSplit, command: Commands): if autosplit.is_auto_controlled: print(command, flush=True) elif command in {"split", "start"}: - _send_hotkey(autosplit.split_input.text()) + _send_hotkey(autosplit.settings_dict["split_hotkey"]) elif command == "pause": - _send_hotkey(autosplit.pause_input.text()) + _send_hotkey(autosplit.settings_dict["pause_hotkey"]) elif command == "reset": - _send_hotkey(autosplit.reset_input.text()) + _send_hotkey(autosplit.settings_dict["reset_hotkey"]) elif command == "skip": - _send_hotkey(autosplit.skip_split_input.text()) + _send_hotkey(autosplit.settings_dict["skip_split_hotkey"]) elif command == "undo": - _send_hotkey(autosplit.undo_split_input.text()) + _send_hotkey(autosplit.settings_dict["undo_split_hotkey"]) else: raise KeyError(f"'{command}' is not a valid LiveSplit.AutoSplitIntegration command") @@ -138,18 +141,19 @@ def __get_key_name(keyboard_event: KeyboardEvent): def __is_key_already_set(autosplit: AutoSplit, key_name: str): - return key_name in (autosplit.split_input.text(), - autosplit.reset_input.text(), - autosplit.skip_split_input.text(), - autosplit.undo_split_input.text(), - autosplit.pause_input.text()) + return key_name in (autosplit.settings_dict["split_hotkey"], + autosplit.settings_dict["reset_hotkey"], + autosplit.settings_dict["skip_split_hotkey"], + autosplit.settings_dict["undo_split_hotkey"], + autosplit.settings_dict["pause_hotkey"]) # --------------------HOTKEYS-------------------------- # TODO: Refactor to de-duplicate all this code, including settings_file.py # Going to comment on one func, and others will be similar. def set_split_hotkey(autosplit: AutoSplit, preselected_key: str = ""): - autosplit.set_split_hotkey_button.setText(PRESS_A_KEY_TEXT) + if autosplit.SettingsWidget: + autosplit.SettingsWidget.set_split_hotkey_button.setText(PRESS_A_KEY_TEXT) # disable some buttons before_setting_hotkey(autosplit) @@ -188,7 +192,9 @@ def callback(): autosplit.split_hotkey = keyboard.hook_key( key_name, lambda error: _hotkey_action(error, key_name, autosplit.start_auto_splitter)) - autosplit.split_input.setText(key_name) + if autosplit.SettingsWidget: + autosplit.SettingsWidget.split_input.setText(key_name) + autosplit.settings_dict["split_hotkey"] = key_name autosplit.after_setting_hotkey_signal.emit() # try to remove the previously set hotkey if there is one. @@ -198,7 +204,8 @@ def callback(): def set_reset_hotkey(autosplit: AutoSplit, preselected_key: str = ""): - autosplit.set_reset_hotkey_button.setText(PRESS_A_KEY_TEXT) + if autosplit.SettingsWidget: + autosplit.SettingsWidget.set_reset_hotkey_button.setText(PRESS_A_KEY_TEXT) before_setting_hotkey(autosplit) def callback(): @@ -215,7 +222,9 @@ def callback(): autosplit.reset_hotkey = keyboard.hook_key( key_name, lambda error: _hotkey_action(error, key_name, autosplit.reset_signal.emit)) - autosplit.reset_input.setText(key_name) + if autosplit.SettingsWidget: + autosplit.SettingsWidget.reset_input.setText(key_name) + autosplit.settings_dict["reset_hotkey"] = key_name autosplit.after_setting_hotkey_signal.emit() _unhook(autosplit.reset_hotkey) @@ -224,7 +233,8 @@ def callback(): def set_skip_split_hotkey(autosplit: AutoSplit, preselected_key: str = ""): - autosplit.set_skip_split_hotkey_button.setText(PRESS_A_KEY_TEXT) + if autosplit.SettingsWidget: + autosplit.SettingsWidget.set_skip_split_hotkey_button.setText(PRESS_A_KEY_TEXT) before_setting_hotkey(autosplit) def callback(): @@ -241,7 +251,9 @@ def callback(): autosplit.skip_split_hotkey = keyboard.hook_key( key_name, lambda error: _hotkey_action(error, key_name, autosplit.skip_split_signal.emit)) - autosplit.skip_split_input.setText(key_name) + if autosplit.SettingsWidget: + autosplit.SettingsWidget.skip_split_input.setText(key_name) + autosplit.settings_dict["skip_split_hotkey"] = key_name autosplit.after_setting_hotkey_signal.emit() _unhook(autosplit.skip_split_hotkey) @@ -250,7 +262,8 @@ def callback(): def set_undo_split_hotkey(autosplit: AutoSplit, preselected_key: str = ""): - autosplit.set_undo_split_hotkey_button.setText(PRESS_A_KEY_TEXT) + if autosplit.SettingsWidget: + autosplit.SettingsWidget.set_undo_split_hotkey_button.setText(PRESS_A_KEY_TEXT) before_setting_hotkey(autosplit) def callback(): @@ -267,7 +280,9 @@ def callback(): autosplit.undo_split_hotkey = keyboard.hook_key( key_name, lambda error: _hotkey_action(error, key_name, autosplit.undo_split_signal.emit)) - autosplit.undo_split_input.setText(key_name) + if autosplit.SettingsWidget: + autosplit.SettingsWidget.undo_split_input.setText(key_name) + autosplit.settings_dict["undo_split_hotkey"] = key_name autosplit.after_setting_hotkey_signal.emit() _unhook(autosplit.undo_split_hotkey) @@ -276,7 +291,8 @@ def callback(): def set_pause_hotkey(autosplit: AutoSplit, preselected_key: str = ""): - autosplit.set_pause_hotkey_button.setText(PRESS_A_KEY_TEXT) + if autosplit.SettingsWidget: + autosplit.SettingsWidget.set_pause_hotkey_button.setText(PRESS_A_KEY_TEXT) before_setting_hotkey(autosplit) def callback(): @@ -293,7 +309,9 @@ def callback(): autosplit.pause_hotkey = keyboard.hook_key( key_name, lambda error: _hotkey_action(error, key_name, autosplit.pause_signal.emit)) - autosplit.pause_input.setText(key_name) + if autosplit.SettingsWidget: + autosplit.SettingsWidget.pause_input.setText(key_name) + autosplit.settings_dict["pause_hotkey"] = key_name autosplit.after_setting_hotkey_signal.emit() _unhook(autosplit.pause_hotkey) diff --git a/src/menu_bar.py b/src/menu_bar.py index 66cb9c85..d90c1e8c 100644 --- a/src/menu_bar.py +++ b/src/menu_bar.py @@ -1,5 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any + if TYPE_CHECKING: from AutoSplit import AutoSplit @@ -14,7 +15,9 @@ import error_messages import settings_file as settings -from gen import about, design, resources_rc, update_checker # noqa: F401 +from capture_windows import Region +from gen import about, design, resources_rc, settings as settings_ui, update_checker # noqa: F401 +from hotkeys import set_split_hotkey, set_reset_hotkey, set_skip_split_hotkey, set_undo_split_hotkey, set_pause_hotkey # AutoSplit Version number VERSION = "1.6.1" @@ -82,7 +85,7 @@ def __init__(self, autosplit: AutoSplit, check_on_open: bool): def run(self): try: response = requests.get("https://api.github.com/repos/Toufool/Auto-Split/releases/latest") - latest_version = response.json()["name"].split("v")[1] + latest_version = str(response.json()["name"]).split("v")[1] self.autosplit.update_checker_widget_signal.emit(latest_version, self.check_on_open) except (RequestException, KeyError, JSONDecodeError): if not self.check_on_open: @@ -92,3 +95,107 @@ def run(self): def check_for_updates(autosplit: AutoSplit, check_on_open: bool = False): autosplit.CheckForUpdatesThread = __CheckForUpdatesThread(autosplit, check_on_open) autosplit.CheckForUpdatesThread.start() + + +class __SettingsWidget(QtWidgets.QDialog, settings_ui.Ui_DialogSettings): + def __init__(self, autosplit: AutoSplit): + super().__init__() + self.setupUi(self) + self.autosplit = autosplit + + def set_value(key: str, value: Any): + autosplit.settings_dict[key] = value + +# region Set initial values + # Hotkeys + self.split_input.setText(autosplit.settings_dict["split_hotkey"]) + self.reset_input.setText(autosplit.settings_dict["reset_hotkey"]) + self.undo_split_input.setText(autosplit.settings_dict["undo_split_hotkey"]) + self.skip_split_input.setText(autosplit.settings_dict["skip_split_hotkey"]) + self.pause_input.setText(autosplit.settings_dict["pause_hotkey"]) + + # Capture Settings + self.fps_limit_spinbox.setValue(autosplit.settings_dict["fps_limit"]) + self.live_capture_region_checkbox.setChecked(autosplit.settings_dict["live_capture_region"]) + self.force_print_window_checkbox.setChecked(autosplit.settings_dict["force_print_window"]) + + # Image Settings + self.default_comparison_method.setCurrentIndex(autosplit.settings_dict["default_comparison_method"]) + self.default_similarity_threshold_spinbox.setValue(autosplit.settings_dict["default_similarity_threshold"]) + self.default_delay_time_spinbox.setValue(autosplit.settings_dict["default_delay_time"]) + self.default_pause_time_spinbox.setValue(autosplit.settings_dict["default_pause_time"]) + self.loop_splits_checkbox.setChecked(autosplit.settings_dict["loop_splits"]) +# endregion +# region Binding + # Hotkeys + self.set_split_hotkey_button.clicked.connect(lambda: set_split_hotkey(self.autosplit)) + self.set_reset_hotkey_button.clicked.connect(lambda: set_reset_hotkey(self.autosplit)) + self.set_skip_split_hotkey_button.clicked.connect(lambda: set_skip_split_hotkey(self.autosplit)) + self.set_undo_split_hotkey_button.clicked.connect(lambda: set_undo_split_hotkey(self.autosplit)) + self.set_pause_hotkey_button.clicked.connect(lambda: set_pause_hotkey(self.autosplit)) + + # Capture Settings + self.fps_limit_spinbox.valueChanged.connect(lambda: set_value( + "fps_limit", + self.fps_limit_spinbox.value())) + self.live_capture_region_checkbox.stateChanged.connect(lambda: set_value( + "live_capture_region", + self.live_capture_region_checkbox.isChecked())) + self.force_print_window_checkbox.stateChanged.connect(lambda: set_value( + "force_print_window", + self.force_print_window_checkbox.isChecked())) + + # Image Settings + self.default_comparison_method.currentIndexChanged.connect(lambda: set_value( + "default_comparison_method", + self.default_comparison_method.currentIndex())) + self.default_similarity_threshold_spinbox.valueChanged.connect(lambda: set_value( + "default_similarity_threshold", + self.default_similarity_threshold_spinbox.value())) + self.default_delay_time_spinbox.valueChanged.connect(lambda: set_value( + "default_delay_time", + self.default_delay_time_spinbox.value())) + self.default_pause_time_spinbox.valueChanged.connect(lambda: set_value( + "default_pause_time", + self.default_pause_time_spinbox.value())) + self.loop_splits_checkbox.stateChanged.connect(lambda: set_value( + "loop_splits", + self.loop_splits_checkbox.isChecked())) +# endregion + + self.show() + + +def open_settings(autosplit: AutoSplit): + autosplit.SettingsWidget = __SettingsWidget(autosplit) + + +def get_default_settings_from_ui(autosplit: AutoSplit): + temp_dialog = QtWidgets.QDialog() + default_settings_dialog = settings_ui.Ui_DialogSettings() + default_settings_dialog.setupUi(temp_dialog) + default_settings: settings.SettingsDict = { + "split_hotkey": default_settings_dialog.split_input.text(), + "reset_hotkey": default_settings_dialog.reset_input.text(), + "undo_split_hotkey": default_settings_dialog.undo_split_input.text(), + "skip_split_hotkey": default_settings_dialog.skip_split_input.text(), + "pause_hotkey": default_settings_dialog.pause_input.text(), + "fps_limit": default_settings_dialog.fps_limit_spinbox.value(), + "live_capture_region": default_settings_dialog.live_capture_region_checkbox.isChecked(), + "force_print_window": default_settings_dialog.force_print_window_checkbox.isChecked(), + "default_comparison_method": default_settings_dialog.default_comparison_method.currentIndex(), + "default_similarity_threshold": default_settings_dialog.default_similarity_threshold_spinbox.value(), + "default_delay_time": default_settings_dialog.default_delay_time_spinbox.value(), + "default_pause_time": default_settings_dialog.default_pause_time_spinbox.value(), + "loop_splits": default_settings_dialog.loop_splits_checkbox.isChecked(), + + "split_image_directory": autosplit.split_image_folder_input.text(), + "captured_window_title": "", + "capture_region": Region( + autosplit.x_spinbox.value(), + autosplit.y_spinbox.value(), + autosplit.width_spinbox.value(), + autosplit.height_spinbox.value()) + } + del temp_dialog + return default_settings diff --git a/src/screen_region.py b/src/screen_region.py index 03bbf85e..2342dd8d 100644 --- a/src/screen_region.py +++ b/src/screen_region.py @@ -45,7 +45,7 @@ def select_region(autosplit: AutoSplit): error_messages.region() return autosplit.hwnd = hwnd - autosplit.window_text = window_text + autosplit.settings_dict["captured_window_title"] = window_text offset_x, offset_y, *_ = win32gui.GetWindowRect(autosplit.hwnd) __set_region_values(autosplit, @@ -76,7 +76,7 @@ def select_window(autosplit: AutoSplit): error_messages.region() return autosplit.hwnd = hwnd - autosplit.window_text = window_text + autosplit.settings_dict["captured_window_title"] = window_text # Getting window bounds # On Windows there is a shadow around the windows that we need to account for @@ -134,8 +134,8 @@ def align_region(autosplit: AutoSplit): # subregion being searched for to align the image. capture = capture_windows.capture_region( autosplit.hwnd, - autosplit.selection, - autosplit.force_print_window_checkbox.isChecked()) + autosplit.settings_dict["capture_region"], + autosplit.settings_dict["force_print_window"]) if capture is None: error_messages.region() @@ -151,25 +151,23 @@ def align_region(autosplit: AutoSplit): # The new region can be defined by using the min_loc point and the best_height and best_width of the template. __set_region_values(autosplit, - left=autosplit.selection.left + best_loc[0], - top=autosplit.selection.top + best_loc[1], + left=autosplit.settings_dict["capture_region"].x + best_loc[0], + top=autosplit.settings_dict["capture_region"].y + best_loc[1], width=best_width, height=best_height) def __set_region_values(autosplit: AutoSplit, left: int, top: int, width: int, height: int): - autosplit.selection.left = left - autosplit.selection.top = top - autosplit.selection.right = left + width - autosplit.selection.bottom = top + height + autosplit.settings_dict["capture_region"].x = left + autosplit.settings_dict["capture_region"].y = top + autosplit.settings_dict["capture_region"].width = width + autosplit.settings_dict["capture_region"].height = height autosplit.x_spinbox.setValue(left) autosplit.y_spinbox.setValue(top) autosplit.width_spinbox.setValue(width) autosplit.height_spinbox.setValue(height) - autosplit.check_live_image() - def __test_alignment(capture: cv2.ndarray, template: cv2.ndarray): # Obtain the best matching point for the template within the @@ -213,11 +211,11 @@ def __test_alignment(capture: cv2.ndarray, template: cv2.ndarray): def validate_before_parsing(autosplit: AutoSplit, show_error: bool = True, check_empty_directory: bool = True): error = None - if not autosplit.split_image_directory: + if not autosplit.settings_dict["split_image_directory"]: error = error_messages.split_image_directory - elif not os.path.isdir(autosplit.split_image_directory): + elif not os.path.isdir(autosplit.settings_dict["split_image_directory"]): error = error_messages.split_image_directory_not_found - elif check_empty_directory and not os.listdir(autosplit.split_image_directory): + elif check_empty_directory and not os.listdir(autosplit.settings_dict["split_image_directory"]): error = error_messages.split_image_directory_empty elif autosplit.hwnd <= 0 or not win32gui.GetWindowText(autosplit.hwnd): error = error_messages.region diff --git a/src/settings_file.py b/src/settings_file.py index c0315c23..cc6fec44 100644 --- a/src/settings_file.py +++ b/src/settings_file.py @@ -1,5 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, TypedDict + if TYPE_CHECKING: from AutoSplit import AutoSplit @@ -12,6 +13,7 @@ from PyQt6 import QtCore, QtWidgets import error_messages +from capture_windows import Region from gen import design from hotkeys import set_pause_hotkey, set_reset_hotkey, set_skip_split_hotkey, set_split_hotkey, set_undo_split_hotkey @@ -21,6 +23,26 @@ auto_split_directory = os.path.dirname(sys.executable if FROZEN else os.path.abspath(__file__)) +class SettingsDict(TypedDict): + split_hotkey: str + reset_hotkey: str + undo_split_hotkey: str + skip_split_hotkey: str + pause_hotkey: str + fps_limit: int + live_capture_region: bool + force_print_window: bool + default_comparison_method: int + default_similarity_threshold: float + default_delay_time: int + default_pause_time: float + loop_splits: bool + + split_image_directory: str + captured_window_title: str + capture_region: Region + + class RestrictedUnpickler(pickle.Unpickler): def find_class(self, module: str, name: str): @@ -29,27 +51,27 @@ def find_class(self, module: str, name: str): def get_save_settings_values(autosplit: AutoSplit): return [ - autosplit.split_image_directory, - autosplit.similarity_threshold_spinbox.value(), - autosplit.comparison_method_combobox.currentIndex(), - autosplit.pause_spinbox.value(), - int(autosplit.fps_limit_spinbox.value()), - autosplit.split_input.text(), - autosplit.reset_input.text(), - autosplit.skip_split_input.text(), - autosplit.undo_split_input.text(), - autosplit.pause_input.text(), - autosplit.x_spinbox.value(), - autosplit.y_spinbox.value(), - autosplit.width_spinbox.value(), - autosplit.height_spinbox.value(), - autosplit.window_text, + autosplit.settings_dict["split_image_directory"], + autosplit.settings_dict["default_similarity_threshold"], + autosplit.settings_dict["default_comparison_method"], + autosplit.settings_dict["default_pause_time"], + autosplit.settings_dict["fps_limit"], + autosplit.settings_dict["split_hotkey"], + autosplit.settings_dict["reset_hotkey"], + autosplit.settings_dict["skip_split_hotkey"], + autosplit.settings_dict["undo_split_hotkey"], + autosplit.settings_dict["pause_hotkey"], + autosplit.settings_dict["capture_region"].x, + autosplit.settings_dict["capture_region"].y, + autosplit.settings_dict["capture_region"].width, + autosplit.settings_dict["capture_region"].height, + autosplit.settings_dict["captured_window_title"], 0, 0, 1, - int(autosplit.loop_checkbox.isChecked()), - int(autosplit.auto_start_on_reset_checkbox.isChecked()), - autosplit.force_print_window_checkbox.isChecked()] + autosplit.settings_dict["loop_splits"], + 0, + autosplit.settings_dict["force_print_window"]] def have_settings_changed(autosplit: AutoSplit): @@ -123,12 +145,12 @@ def __load_settings_from_file(autosplit: AutoSplit, load_settings_file_path: str autosplit.show_error_signal.emit(error_messages.invalid_settings) return False - autosplit.split_image_directory = settings[0] + autosplit.settings_dict["split_image_directory"] = settings[0] autosplit.split_image_folder_input.setText(settings[0]) - autosplit.similarity_threshold_spinbox.setValue(settings[1]) - autosplit.comparison_method_combobox.setCurrentIndex(settings[2]) - autosplit.pause_spinbox.setValue(settings[3]) - autosplit.fps_limit_spinbox.setValue(settings[4]) + autosplit.settings_dict["default_similarity_threshold"] = settings[1] + autosplit.settings_dict["default_comparison_method"] = settings[2] + autosplit.settings_dict["default_pause_time"] = settings[3] + autosplit.settings_dict["fps_limit"] = settings[4] keyboard.unhook_all() if not autosplit.is_auto_controlled: set_split_hotkey(autosplit, settings[5]) @@ -140,20 +162,19 @@ def __load_settings_from_file(autosplit: AutoSplit, load_settings_file_path: str autosplit.y_spinbox.setValue(settings[11]) autosplit.width_spinbox.setValue(settings[12]) autosplit.height_spinbox.setValue(settings[13]) - autosplit.window_text = settings[14] - autosplit.loop_checkbox.setChecked(bool(settings[18])) - autosplit.auto_start_on_reset_checkbox.setChecked(bool(settings[19])) - autosplit.force_print_window_checkbox.setChecked(settings[20]) + autosplit.settings_dict["captured_window_title"] = settings[14] + autosplit.settings_dict["loop_splits"] = settings[18] + autosplit.settings_dict["force_print_window"] = settings[20] - if autosplit.window_text: + if autosplit.settings_dict["captured_window_title"]: # https://github.com/kaluluosi/pywin32-stubs/issues/7 - hwnd = win32gui.FindWindow(None, autosplit.window_text) # type: ignore + hwnd = win32gui.FindWindow(None, autosplit.settings_dict["captured_window_title"]) # type: ignore if hwnd: autosplit.hwnd = hwnd else: autosplit.live_image.setText("Reload settings after opening" - f'\n"{autosplit.window_text}"' - "\nto automatically load Live Capture") + + f'\n"{autosplit.settings_dict["captured_window_title"]}"' + + "\nto automatically load Capture Region") return True @@ -170,7 +191,6 @@ def load_settings( return autosplit.last_successfully_loaded_settings_file_path = load_settings_file_path - autosplit.check_live_image() autosplit.load_start_image() diff --git a/src/split_parser.py b/src/split_parser.py index 27862ed9..6e4a584f 100644 --- a/src/split_parser.py +++ b/src/split_parser.py @@ -153,9 +153,9 @@ def __pop_image_type(split_image: list[AutoSplitImage], image_type: ImageType): def parse_and_validate_images(autosplit: AutoSplit): # Get split images all_images = [ - AutoSplitImage(os.path.join(autosplit.split_image_directory, image_name)) + AutoSplitImage(os.path.join(autosplit.settings_dict["split_image_directory"], image_name)) for image_name - in os.listdir(autosplit.split_image_directory)] + in os.listdir(autosplit.settings_dict["split_image_directory"])] # Find non-split images and then remove them from the list autosplit.start_image = __pop_image_type(all_images, ImageType.START) @@ -171,7 +171,7 @@ def parse_and_validate_images(autosplit: AutoSplit): return False # error out if there is a {p} flag but no pause hotkey set and is not auto controlled. - if (not autosplit.pause_input.text() + if (not autosplit.settings_dict["pause_hotkey"] and image.check_flag(PAUSE_FLAG) and not autosplit.is_auto_controlled): autosplit.gui_changes_on_reset() @@ -181,7 +181,7 @@ def parse_and_validate_images(autosplit: AutoSplit): # Check that there's only one reset image if image.image_type == ImageType.RESET: # If there is no reset hotkey set but a reset image is present, and is not auto controlled, throw an error. - if not autosplit.reset_input.text() and not autosplit.is_auto_controlled: + if not autosplit.settings_dict["reset_hotkey"] and not autosplit.is_auto_controlled: autosplit.gui_changes_on_reset() error_messages.reset_hotkey() return False From f0149f5efcd1c346abe0c7c9a1f08bbcbdd415e1 Mon Sep 17 00:00:00 2001 From: Avasam Date: Wed, 15 Dec 2021 18:24:08 -0500 Subject: [PATCH 2/7] Implemented image specific comparison method and default delay time. Closes #26 --- src/AutoSplit.py | 8 ++--- src/AutoSplitImage.py | 41 +++++++++++++++++-------- src/split_parser.py | 71 ++++++++++++++++++++++++++++--------------- 3 files changed, 79 insertions(+), 41 deletions(-) diff --git a/src/AutoSplit.py b/src/AutoSplit.py index 64f73d2b..395468da 100644 --- a/src/AutoSplit.py +++ b/src/AutoSplit.py @@ -342,10 +342,10 @@ def __start_image_function(self): self.start_image_split_below_threshold = False # delay start image if needed - if self.start_image.delay > 0: + if self.start_image.get_delay_time(self) > 0: self.start_image_status_value_label.setText("delaying start...") delay_start_time = time() - start_delay = self.start_image.delay / 1000 + start_delay = self.start_image.get_delay_time(self) / 1000 while time() - delay_start_time < start_delay: delay_time_left = round(start_delay - (time() - delay_start_time), 1) self.current_split_image.setText( @@ -398,7 +398,7 @@ def __take_screenshot(self): # save and open image cv2.imwrite(screenshot_path, capture) - os.startfile(screenshot_path) + os.startfile(screenshot_path) # nosec def __check_fps(self): self.fps_value_label.clear() @@ -625,7 +625,7 @@ def __auto_splitter(self): if not self.split_image.check_flag(DUMMY_FLAG): # If it's a delayed split, check if the delay has passed # Otherwise calculate the split time for the key press - split_delay = self.split_image.delay / 1000 + split_delay = self.split_image.get_delay_time(self) / 1000 if split_delay > 0 and not self.waiting_for_split_delay: split_time = round(time() + split_delay * 1000) self.waiting_for_split_delay = True diff --git a/src/AutoSplitImage.py b/src/AutoSplitImage.py index 9eeaf8a9..0a99c5b9 100644 --- a/src/AutoSplitImage.py +++ b/src/AutoSplitImage.py @@ -30,21 +30,40 @@ class AutoSplitImage(): filename: str flags: int loops: int - delay: float image_type: ImageType bytes: Optional[cv2.ndarray] = None mask: Optional[cv2.ndarray] = None # This value is internal, check for mask instead _has_transparency: bool # These values should be overriden by Defaults if None. Use getters instead + __delay_time: Optional[float] = None + __comparison_method: Optional[int] = None __pause_time: Optional[float] = None __similarity_threshold: Optional[float] = None + def get_delay_time(self, default: Union[AutoSplit, int]): + """ + Get image's delay time or fallback to the default value from spinbox + """ + default_value = default \ + if isinstance(default, int) \ + else default.settings_dict["default_delay_time"] + return default_value if self.__delay_time is None else self.__delay_time + + def __get_comparison_method(self, default: Union[AutoSplit, int]): + """ + Get image's comparison or fallback to the default value from combobox + """ + default_value = default \ + if isinstance(default, int) \ + else default.settings_dict["default_comparison_method"] + return default_value if self.__comparison_method is None else self.__comparison_method + def get_pause_time(self, default: Union[AutoSplit, float]): """ Get image's pause time or fallback to the default value from spinbox """ - default_value: float = default \ + default_value = default \ if isinstance(default, float) \ else default.settings_dict["default_pause_time"] return default_value if self.__pause_time is None else self.__pause_time @@ -53,7 +72,7 @@ def get_similarity_threshold(self, default: Union[AutoSplit, float]): """ Get image's similarity threashold or fallback to the default value from spinbox """ - default_value: float = default \ + default_value = default \ if isinstance(default, float) \ else default.settings_dict["default_similarity_threshold"] return default_value if self.__similarity_threshold is None else self.__similarity_threshold @@ -63,7 +82,8 @@ def __init__(self, path: str): self.filename = os.path.split(path)[-1].lower() self.flags = flags_from_filename(self.filename) self.loops = loop_from_filename(self.filename) - self.delay = delay_from_filename(self.filename) + self.__delay_time = delay_time_from_filename(self.filename) + self.__comparison_method = comparison_method_from_filename(self.filename) self.__pause_time = pause_from_filename(self.filename) self.__similarity_threshold = threshold_from_filename(self.filename) self.__read_image_bytes(path) @@ -101,18 +121,15 @@ def check_flag(self, flag: int): def compare_with_capture( self, - comparison: Union[AutoSplit, int], + default: Union[AutoSplit, int], capture: Optional[cv2.ndarray] ): """ - Compare image with capture using comparison method from combobox + Compare image with capture using image's comparison method. Falls back to combobox """ - comparison_method: int = comparison \ - if isinstance(comparison, int) \ - else comparison.settings_dict["default_comparison_method"] - if self.bytes is None or capture is None: return 0.0 + comparison_method = self.__get_comparison_method(default) if comparison_method == 0: return compare_l2_norm(self.bytes, capture, self.mask) if comparison_method == 1: @@ -122,5 +139,5 @@ def compare_with_capture( return 0.0 -from split_parser import delay_from_filename, flags_from_filename, loop_from_filename, pause_from_filename, \ - threshold_from_filename +from split_parser import comparison_method_from_filename, delay_time_from_filename, flags_from_filename, \ + loop_from_filename, pause_from_filename, threshold_from_filename diff --git a/src/split_parser.py b/src/split_parser.py index 6e4a584f..73247626 100644 --- a/src/split_parser.py +++ b/src/split_parser.py @@ -1,5 +1,5 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, TypeVar if TYPE_CHECKING: from AutoSplit import AutoSplit @@ -14,6 +14,22 @@ PAUSE_FLAG, *_] = [1 << i for i in range(31)] # 32 bits of flags +T = TypeVar("T", str, int, float) + + +def __value_from_filename( + filename: str, + delimiters: str, + default_value: T +) -> T: + if len(delimiters) != 2: + raise ValueError("delimiters parameter must contain exactly 2 characters") + try: + value_type = type(default_value) + return value_type(filename.split(delimiters[0], 1)[1].split(delimiters[1])[0]) + except (IndexError, ValueError): + return default_value + def threshold_from_filename(filename: str): """ @@ -26,13 +42,10 @@ def threshold_from_filename(filename: str): # Check to make sure there is a valid floating point number between # parentheses of the filename - try: - threshold = float(filename.split("(", 1)[1].split(")")[0]) - except (IndexError, ValueError): - return None + value = __value_from_filename(filename, "()", -1.0) # Check to make sure if it is a valid threshold - return threshold if 0.0 < threshold < 1.0 else None + return value if 0.0 < value < 1.0 else None def pause_from_filename(filename: str): @@ -46,16 +59,13 @@ def pause_from_filename(filename: str): # Check to make sure there is a valid pause time between brackets # of the filename - try: - pause = float(filename.split("[", 1)[1].split("]")[0]) - except (IndexError, ValueError): - return None + value = __value_from_filename(filename, "[]", -1.0) # Pause times should always be positive or zero - return pause if pause >= 0.0 else None + return value if value >= 0.0 else None -def delay_from_filename(filename: str): +def delay_time_from_filename(filename: str): """ Retrieve the delay time from the filename, if there is no delay time or the delay time isn't a valid number, then 0 is returned @@ -66,13 +76,10 @@ def delay_from_filename(filename: str): # Check to make sure there is a valid delay time between brackets # of the filename - try: - delay = float(filename.split("#", 1)[1].split("#")[0]) - except (IndexError, ValueError): - return 0.0 + value = __value_from_filename(filename, "##", 0) # Delay times should always be positive or zero - return delay if delay >= 0.0 else 0.0 + return value if value >= 0 else None def loop_from_filename(filename: str): @@ -86,13 +93,27 @@ def loop_from_filename(filename: str): # Check to make sure there is a valid delay time between brackets # of the filename - try: - loop = int(filename.split("@", 1)[1].split("@")[0]) - except (IndexError, ValueError): - return 1 + value = __value_from_filename(filename, "@@", 1) # Loop should always be positive - return loop if loop >= 1 else 1 + return value if value >= 1 else 1 + + +def comparison_method_from_filename(filename: str): + """ + Retrieve the number of loops from filename, if there is no loop number or the loop number isn't valid, + then 1 is returned. + + @param filename: String containing the file's name + @return: A valid loop number, if not then 1 + """ + + # Check to make sure there is a valid delay time between brackets + # of the filename + value = __value_from_filename(filename, "<>", 0) + + # Comparison method should always be positive or zero + return value if value >= 0 else None def flags_from_filename(filename: str): @@ -110,9 +131,9 @@ def flags_from_filename(filename: str): # Check to make sure there are flags between curly braces # of the filename - try: - flags_str = filename.split("{", 1)[1].split("}")[0] - except (IndexError, ValueError): + flags_str = __value_from_filename(filename, "{}", "") + + if not flags_str: return 0 flags = 0x00 From 9be8afc3bb7dc3938ee8d567b9f07340991c592b Mon Sep 17 00:00:00 2001 From: Austin <37423484+Toufool@users.noreply.github.com> Date: Mon, 13 Dec 2021 19:44:50 -0500 Subject: [PATCH 3/7] Initial UI update for 2.0.0. Add settings window. --- res/design.ui | 1261 ++++++++++++++++++----------------------------- res/settings.ui | 717 +++++++++++++++++++++++++++ 2 files changed, 1192 insertions(+), 786 deletions(-) create mode 100644 res/settings.ui diff --git a/res/design.ui b/res/design.ui index fea8611c..80334881 100644 --- a/res/design.ui +++ b/res/design.ui @@ -1,13 +1,13 @@ - MainWindow - + main_window + 0 0 - 632 - 490 + 777 + 424 @@ -18,14 +18,14 @@ - 632 - 490 + 777 + 424 - 632 - 490 + 777 + 424 @@ -37,7 +37,7 @@ AutoSplit - + :/resources/icon.ico:/resources/icon.ico @@ -47,53 +47,11 @@ Qt::LeftToRight - - - - 20 - 12 - 98 - 16 - - - - Split Image Folder: - - - - - - 130 - 10 - 412 - 22 - - - - true - - - - - - 540 - 9 - 75 - 24 - - - - Qt::NoFocus - - - Browse... - - - 25 - 145 + 30 + 143 7 16 @@ -102,33 +60,11 @@ X - - - true - - - - 120 - 252 - 129 - 20 - - - - Live Capture Region - - - true - - - false - - - 5 - 70 + 10 + 67 101 23 @@ -140,45 +76,13 @@ Select Region - - - - 7 - 410 - 151 - 16 - - - - Default similarity threshold: - - - - - - 155 - 408 - 61 - 22 - - - - 1.000000000000000 - - - 0.010000000000000 - - - 0.900000000000000 - - - 500 - 425 + 650 + 369 121 - 31 + 27 @@ -189,15 +93,12 @@ - - false - - 500 - 390 + 650 + 339 121 - 31 + 27 @@ -207,78 +108,62 @@ Reset - - - false - + - 340 - 440 - 64 - 21 + 650 + 310 + 59 + 27 Qt::NoFocus - Undo Split + Undo - - - false - + - 410 - 440 - 61 - 21 + 712 + 310 + 59 + 27 Qt::NoFocus - Skip Split - - - - - - 7 - 439 - 131 - 16 - - - - Default pause time (sec): + Skip - 5 - 225 - 54 + 10 + 253 + 53 23 Qt::NoFocus + + calculate the average max FPS of the set capture region + - Avg. FPS + Max FPS - 87 - 225 + 92 + 255 20 20 @@ -287,290 +172,17 @@ FPS - - - true - - - - 7 - 323 - 124 - 20 - - - - Show live similarity - - - true - - - false - - - - - true - - - - 7 - 350 - 145 - 20 - - - - Show highest similarity - - - true - - - false - - - - - - 171 - 326 - 46 - 16 - - - - - - - - - - 171 - 352 - 46 - 16 - - - - - - - - - - 230 - 317 - 58 - 16 - - - - Start / Split - - - - - - 230 - 341 - 28 - 16 - - - - Reset - - - - - - 230 - 367 - 48 - 16 - - - - Skip Split - - - - - - 230 - 393 - 55 - 16 - - - - Undo Split - - - - - - 300 - 314 - 81 - 20 - - - - true - - - - - - 300 - 391 - 81 - 20 - - - - Qt::StrongFocus - - - true - - - - - - 300 - 365 - 81 - 20 - - - - true - - - - - - 300 - 339 - 81 - 20 - - - - true - - - - - - 390 - 314 - 81 - 21 - - - - Qt::NoFocus - - - Set Hotkey - - - - - - 390 - 339 - 81 - 21 - - - - Qt::NoFocus - - - Set Hotkey - - - - - - 390 - 365 - 81 - 21 - - - - Qt::NoFocus - - - Set Hotkey - - - - - - 390 - 391 - 81 - 21 - - - - Qt::NoFocus - - - Set Hotkey - - - - - - 220 - 296 - 2 - 163 - - - - QFrame::Plain - - - 1 - - - Qt::Vertical - - - + - 230 - 291 - 251 - 20 + 119 + 68 + 320 + 240 - - Timer Global Hotkeys - - - Qt::AlignCenter - - - - - - 490 - 296 - 2 - 163 - + + QFrame::Box QFrame::Plain @@ -578,36 +190,20 @@ 1 - - Qt::Vertical - - - - - - 120 - 70 - 240 - 180 - - - - QFrame::Box + + 0 - - Qt::AlignCenter - - 380 - 70 - 240 - 180 + 450 + 68 + 320 + 240 @@ -616,28 +212,28 @@ - - Qt::AlignCenter - - + - 450 - 38 - 102 - 16 + 451 + 31 + 318 + 20 - Current Split Image + Current Image + + + Qt::AlignCenter - 12 - 185 + 17 + 183 33 16 @@ -649,8 +245,8 @@ - 66 - 185 + 70 + 183 41 16 @@ -662,20 +258,20 @@ - 60 - 225 + 65 + 255 26 20 - + 9999 - 6 + 11 200 44 22 @@ -694,7 +290,7 @@ - 62 + 66 200 44 22 @@ -713,68 +309,30 @@ - 200 - 38 - 82 - 16 + 119 + 31 + 319 + 20 Capture Region - - - - - 8 - 252 - 51 - 16 - - - - FPS Limit: - - - - - - 62 - 250 - 44 - 22 - - - - - - - 30 - - - 5000 - - - 1 - - - 60 - - - 0 + + Qt::AlignCenter - + - 380 - 50 - 241 + 478 + 49 + 264 20 - + Image Filename Qt::AlignCenter @@ -783,8 +341,8 @@ - 260 - 250 + 10 + 227 101 23 @@ -799,7 +357,7 @@ - 6 + 11 160 44 22 @@ -827,7 +385,7 @@ - 62 + 66 160 44 22 @@ -852,8 +410,8 @@ - 81 - 145 + 85 + 143 7 16 @@ -862,68 +420,11 @@ Y - - - - 125 - 295 - 91 - 23 - - - - - L2 Norm - - - - - Histograms - - - - - pHash - - - - - - - 155 - 437 - 61 - 22 - - - - 999999999.000000000000000 - - - 1.000000000000000 - - - 10.000000000000000 - - - - - - 7 - 298 - 110 - 16 - - - - Comparison Method - - - 5 - 95 + 10 + 119 101 23 @@ -931,6 +432,9 @@ Qt::NoFocus + + Align the capture region using a reference image + Align Region @@ -938,8 +442,8 @@ - 5 - 120 + 10 + 93 101 23 @@ -951,112 +455,335 @@ Select Window - + - 379 - 252 - 113 - 20 + 696 + 5 + 75 + 24 + + Qt::NoFocus + - Image Loop: + Browse... - + - 230 - 418 - 31 + 10 + 9 + 98 16 - Pause + Split Image Folder: - + - 300 - 416 - 81 - 20 + 119 + 6 + 574 + 22 - - Qt::StrongFocus - true - + - 390 - 416 - 81 - 21 + 120 + 49 + 318 + 20 - - Qt::NoFocus - - Set Hotkey + Window Name - - - - true + + Qt::AlignCenter + + - 500 - 340 - 117 + 450 + 309 + 67 20 - Loop Split Images - - - false - - - false + Image Loop: - + - 500 - 360 - 126 - 20 + 120 + 312 + 321 + 84 - - Auto Start On Reset - - - false + + Similarity Viewer - - false + + Qt::AlignCenter + + + + 133 + 22 + 57 + 16 + + + + Live + + + Qt::AlignCenter + + + + + + 197 + 22 + 57 + 16 + + + + Highest + + + Qt::AlignCenter + + + + + + 261 + 22 + 56 + 16 + + + + Threshold + + + Qt::AlignCenter + + + + + + 0 + 39 + 322 + 3 + + + + Qt::Horizontal + + + + + + 10 + 42 + 111 + 16 + + + + Current Image + + + + + + 10 + 64 + 111 + 16 + + + + Reset Image + + + + + + 0 + 61 + 322 + 3 + + + + Qt::Horizontal + + + + + + 129 + 20 + 3 + 62 + + + + Qt::Vertical + + + + + + 193 + 20 + 3 + 95 + + + + Qt::Vertical + + + + + + 257 + 20 + 3 + 95 + + + + Qt::Vertical + + + + + + 133 + 43 + 57 + 16 + + + + - + + + Qt::AlignCenter + + + + + + 197 + 43 + 57 + 16 + + + + - + + + Qt::AlignCenter + + + + + + 260 + 43 + 57 + 16 + + + + - + + + Qt::AlignCenter + + + + + + 133 + 64 + 57 + 16 + + + + - + + + Qt::AlignCenter + + + + + + 197 + 64 + 57 + 16 + + + + - + + + Qt::AlignCenter + + + + + + 260 + 64 + 57 + 16 + + + + - + + + Qt::AlignCenter + + - + - 500 - 302 + 450 + 369 121 - 31 + 27 @@ -1066,150 +793,92 @@ Reload Start Image - - - - 440 - 270 - 181 - 20 - - - - Start image: - - - + - 7 - 380 - 151 + 450 + 345 + 98 16 - Current similarity threshold: + Start Image Status: - + - 171 - 380 - 46 + 551 + 345 + 98 16 - - - - - - - 120 - 50 - 241 - 20 - - - - - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + status - - - true - + - 120 - 270 - 221 + 519 + 309 + 131 20 - Force Full Content Rendering (slower) + x/x - - - false - + - 560 - 250 - 61 - 23 + 449 + 49 + 27 + 18 Qt::NoFocus + + Previous image + - Next Img. + < - - - false - + - 494 - 250 - 64 - 23 + 744 + 49 + 27 + 18 Qt::NoFocus + + Next image + - Prev. Img. + > - split_image_folder_label - split_image_folder_input - browse_button x_label - live_image_checkbox select_region_button - similarity_threshold_label - similarity_threshold_spinbox start_auto_splitter_button reset_button - undo_split_button - skip_split_button - pause_label + undo_button + skip_button check_fps_button fps_label - show_live_similarity_checkbox - show_highest_similarity_checkbox - live_similarity_label - highest_similarity_label - split_label - reset_label - skip_split_label - undo_split_label - split_input - undo_split_input - skip_split_input - reset_input - set_split_hotkey_button - set_reset_hotkey_button - set_skip_split_hotkey_button - set_undo_split_hotkey_button - left_line - timer_global_hotkeys_label - right_line - current_split_image_label + current_image_label live_image current_split_image width_label @@ -1218,39 +887,32 @@ width_spinbox height_spinbox capture_region_label - fps_limit_label - fps_limit_spinbox - current_split_image_file_label + current_image_file_label take_screenshot_button x_spinbox y_spinbox y_label - comparison_method_combobox - pause_spinbox - comparison_method_label align_region_button select_window_button - image_loop_label - pause_hotkey_label - pause_input - set_pause_hotkey_button - loop_checkbox - auto_start_on_reset_checkbox - start_image_reload_button - start_image_label - current_similarity_threshold_label - current_similarity_threshold_number_label + browse_button + split_image_folder_label + split_image_folder_input capture_region_window_label - force_print_window_checkbox - next_image_button + image_loop_label + similarity_viewer_groupbox + reload_start_image_button + start_image_status_label + start_image_status_value_label + image_loop_value_label previous_image_button + next_image_button 0 0 - 632 + 777 22 @@ -1259,17 +921,19 @@ Help - + File - - - + + + + + @@ -1284,27 +948,32 @@ About - + + + Split Image Settings + + + - Save Settings + Save Profile - + - Load Settings + Load Profile - + - Save Settings As... + Save Profile As... - Check for Updates... + Check For Updates - + true @@ -1318,6 +987,37 @@ Check for Updates on Open + + + Force Full Render + + + + + Check For Updates On Open + + + + + Dummy Splits When Undoing/Skipping + + + + + Settings + + + + + true + + + true + + + Check For Updates On Open + + split_image_folder_input @@ -1325,20 +1025,9 @@ y_spinbox width_spinbox height_spinbox - fps_limit_spinbox - live_image_checkbox - comparison_method_combobox - show_live_similarity_checkbox - show_highest_similarity_checkbox - similarity_threshold_spinbox - pause_spinbox - split_input - reset_input - skip_split_input - undo_split_input - + diff --git a/res/settings.ui b/res/settings.ui new file mode 100644 index 00000000..22aa4a35 --- /dev/null +++ b/res/settings.ui @@ -0,0 +1,717 @@ + + + dialog_settings + + + + 0 + 0 + 289 + 570 + + + + + 0 + 0 + + + + + 289 + 570 + + + + + 289 + 570 + + + + ArrowCursor + + + Settings + + + false + + + false + + + + + 127 + 538 + 156 + 24 + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Save + + + + + + 10 + 180 + 271 + 101 + + + + Capture Settings + + + + true + + + + 6 + 73 + 204 + 20 + + + + Force a full render of the capture window, which is required for some applications to be captured + + + + + + Force Full Render (slower Max FPS) + + + false + + + false + + + + + + 138 + 25 + 51 + 22 + + + + + + + 0 + + + 30.000000000000000 + + + 5000.000000000000000 + + + 1.000000000000000 + + + 60.000000000000000 + + + + + + 6 + 27 + 121 + 16 + + + + This value will limit the amount of frames per second that AutoSplit will run comparisons + + + + + + Comparison FPS Limit: + + + + + true + + + + 6 + 49 + 129 + 20 + + + + Live Capture Region + + + true + + + false + + + + + + + 10 + 290 + 271 + 241 + + + + Image Settings + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + false + + + false + + + + + 167 + 26 + 88 + 22 + + + + + + + + + + + L2 Norm + + + + + Histograms + + + + + pHash + + + + + + + 6 + 29 + 161 + 16 + + + + Default Comparison Method: + + + + + + 6 + 117 + 161 + 16 + + + + + + + Default Pause Time (sec): + + + + + + 167 + 114 + 87 + 22 + + + + The amount of time in seconds that comparison will be paused before moving to the next image. + + + 2 + + + 999999999.000000000000000 + + + 1.000000000000000 + + + 10.000000000000000 + + + + + + 6 + 58 + 151 + 16 + + + + + + + + + + Default Similarity Threshold: + + + + + + 167 + 56 + 52 + 22 + + + + Threshold that the live similarity will need to go above to consider the image a match. + + + 1.000000000000000 + + + 0.010000000000000 + + + 0.900000000000000 + + + + + true + + + + 6 + 144 + 235 + 20 + + + + Loop Last Split Image to First Split Image + + + false + + + false + + + + + + 6 + 173 + 261 + 61 + + + + + 8 + true + + + + teset + + + <html><head/><body><p>Custom image settings and flags are set in the <br></br> image file name. These will override the default <br></br> values. View the <a href="https://github.com/Toufool/Auto-Split#readme"><span style=" text-decoration: underline; color:#0000ff;">README</span></a> for full details on all <br></br> available custom image settings</p></body></html> + + + + + + 6 + 88 + 161 + 16 + + + + + + + + + + Default Delay Time (ms): + + + + + + 167 + 85 + 87 + 22 + + + + After an image is matched, this is the amount of time in millseconds that will be delayed before splitting. + + + + + + 0 + + + 0.000000000000000 + + + 36000000.000000000000000 + + + 1.000000000000000 + + + 0.000000000000000 + + + + + + + 10 + 10 + 271 + 161 + + + + Hotkeys + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + false + + + false + + + + + 180 + 133 + 81 + 21 + + + + Qt::NoFocus + + + Set Hotkey + + + + + + 76 + 28 + 94 + 20 + + + + + + + false + + + + + + 76 + 80 + 94 + 20 + + + + Qt::StrongFocus + + + + + + true + + + + + + 6 + 31 + 71 + 16 + + + + Start / Split: + + + + + + 76 + 53 + 94 + 20 + + + + IBeamCursor + + + + + + true + + + true + + + + + + 180 + 80 + 81 + 21 + + + + Qt::NoFocus + + + Set Hotkey + + + + + + 6 + 55 + 41 + 16 + + + + Reset: + + + + + + 180 + 53 + 81 + 21 + + + + Qt::NoFocus + + + Set Hotkey + + + + + + 180 + 28 + 81 + 21 + + + + Qt::NoFocus + + + Set Hotkey + + + + + + 6 + 132 + 41 + 16 + + + + Pause: + + + + + + 76 + 133 + 94 + 20 + + + + Qt::StrongFocus + + + + + + true + + + + + + 6 + 82 + 71 + 16 + + + + Undo Split: + + + + + + 180 + 106 + 81 + 21 + + + + Qt::NoFocus + + + Set Hotkey + + + + + + 6 + 108 + 61 + 16 + + + + Skip Split: + + + + + + 76 + 106 + 94 + 20 + + + + + + + true + + + + + + split_input + reset_input + undo_split_input + skip_split_input + pause_input + default_comparison_method + default_similarity_threshold_double_spinbox + default_pause_time_double_spinbox + loop_splits_checkbox + fps_limit_spinbox + live_capture_region_checkbox + force_print_window_checkbox + + + + + save_cancel_dialog_button_box + accepted() + dialog_settings + accept() + + + 194 + 541 + + + 140 + 282 + + + + + save_cancel_dialog_button_box + rejected() + dialog_settings + reject() + + + 194 + 541 + + + 140 + 282 + + + + + From 2d8cb6b4ce0e48d11b84f5d7b79f7d6b68e3cb13 Mon Sep 17 00:00:00 2001 From: Avasam Date: Tue, 14 Dec 2021 22:58:19 -0500 Subject: [PATCH 4/7] hooked new settings window to actual values --- .flake8 | 2 +- README.md | 1 - pyproject.toml | 5 + res/design.ui | 61 +++++--- res/settings.ui | 135 +++++------------- scripts/compile_resources.bat | 1 + scripts/designer.bat | 7 +- src/AutoSplit.py | 262 +++++++++++++++------------------- src/AutoSplitImage.py | 6 +- src/capture_windows.py | 25 ++-- src/error_messages.py | 10 +- src/hotkeys.py | 88 +++++++----- src/menu_bar.py | 113 ++++++++++++++- src/screen_region.py | 28 ++-- src/settings_file.py | 86 ++++++----- src/split_parser.py | 8 +- 16 files changed, 458 insertions(+), 380 deletions(-) diff --git a/.flake8 b/.flake8 index 65910611..2715e740 100644 --- a/.flake8 +++ b/.flake8 @@ -12,7 +12,7 @@ per-file-ignores= __init__.pyi:Q000 ; PyQt methods ignore-names=closeEvent,paintEvent,keyPressEvent,mousePressEvent,mouseMoveEvent,mouseReleaseEvent -; McCabe max-complexity is also taken care of by Pylint and doesn't fail teh build there +; McCabe max-complexity is also taken care of by Pylint and doesn't fail the build there ; So this is the hard limit max-complexity=32 inline-quotes=" diff --git a/README.md b/README.md index 2e43e50d..4084e5e5 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,6 @@ This program can be used to automatically start, split, and reset your preferred - {d} dummy split image. When matched, it moves to the next image without hitting your split hotkey. - {b} split when similarity goes below the threshold rather than above. When a split image filename has this flag, the split image similarity will go above the threshold, do nothing, and then split the next time the similarity goes below the threshold. - {p} pause flag. When a split image filename has this flag, it will hit your pause hotkey rather than your split hokey. - - A pause flag and a dummy flag `{pd}` cannot be used together - Filename examples: - `001_SplitName_(0.9)_[10].png` is a split image with a threshold of 0.9 and a pause time of 10 seconds. - `002_SplitName_(0.9)_[10]_{d}.png` is the second split image with a threshold of 0.9, pause time of 10, and is a dummy split. diff --git a/pyproject.toml b/pyproject.toml index 8001ad5f..3f71a85f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,6 +15,11 @@ aggressive = 3 [tool.pyright] pythonPlatform = "Windows" typeCheckingMode = "strict" +# Extra strict +reportPropertyTypeMismatch=true +reportUninitializedInstanceVariable=true +reportCallInDefaultInitializer=true +reportImplicitStringConcatenation=true ignore = [ # Auto generated "src/gen/", diff --git a/res/design.ui b/res/design.ui index 80334881..ea0037ef 100644 --- a/res/design.ui +++ b/res/design.ui @@ -1,7 +1,7 @@ - main_window - + MainWindow + 0 @@ -37,7 +37,7 @@ AutoSplit - + :/resources/icon.ico:/resources/icon.ico @@ -93,6 +93,9 @@ + + false + 650 @@ -108,7 +111,10 @@ Reset - + + + false + 650 @@ -124,7 +130,10 @@ Undo - + + + false + 712 @@ -196,6 +205,9 @@ + + Qt::AlignCenter + @@ -212,6 +224,9 @@ + + Qt::AlignCenter + @@ -265,7 +280,7 @@ - 9999 + @@ -332,7 +347,7 @@ - Image Filename + - Qt::AlignCenter @@ -507,7 +522,7 @@ - Window Name + - Qt::AlignCenter @@ -516,8 +531,8 @@ - 450 - 309 + 451 + 313 67 20 @@ -796,8 +811,8 @@ - 450 - 345 + 449 + 344 98 16 @@ -809,8 +824,8 @@ - 551 - 345 + 550 + 344 98 16 @@ -822,17 +837,20 @@ - 519 - 309 + 520 + 313 131 20 - x/x + N/A + + false + 449 @@ -852,6 +870,9 @@ + + false + 744 @@ -874,8 +895,8 @@ select_region_button start_auto_splitter_button reset_button - undo_button - skip_button + undo_split_button + skip_split_button check_fps_button fps_label current_image_label @@ -1027,7 +1048,7 @@ height_spinbox - + diff --git a/res/settings.ui b/res/settings.ui index 22aa4a35..a388d7d6 100644 --- a/res/settings.ui +++ b/res/settings.ui @@ -1,13 +1,13 @@ - dialog_settings - + DialogSettings + 0 0 289 - 570 + 540 @@ -19,13 +19,13 @@ 289 - 570 + 540 289 - 570 + 540 @@ -40,19 +40,6 @@ false - - - - 127 - 538 - 156 - 24 - - - - QDialogButtonBox::Cancel|QDialogButtonBox::Save - - @@ -93,7 +80,7 @@ false - + 138 @@ -102,23 +89,17 @@ 22 - - - - - 0 + + ArrowCursor - 30.000000000000000 + 20 - 5000.000000000000000 - - - 1.000000000000000 + 240 - 60.000000000000000 + 60 @@ -244,7 +225,7 @@ Default Pause Time (sec): - + 167 @@ -288,7 +269,7 @@ Default Similarity Threshold: - + 167 @@ -332,7 +313,7 @@ false - + 6 @@ -351,10 +332,10 @@ teset - <html><head/><body><p>Custom image settings and flags are set in the <br></br> image file name. These will override the default <br></br> values. View the <a href="https://github.com/Toufool/Auto-Split#readme"><span style=" text-decoration: underline; color:#0000ff;">README</span></a> for full details on all <br></br> available custom image settings</p></body></html> + <html><head/><body><p>Custom image settings and flags are set in the <br></br> image file name. These will override the default <br></br> values. View the <a href="https://github.com/Toufool/Auto-Split#readme"><span style=" text-decoration: underline; color:#0000ff;">README</span></a> for full details on all <br></br> available custom image settings.</p></body></html> - + 6 @@ -373,7 +354,7 @@ Default Delay Time (ms): - + 167 @@ -382,26 +363,14 @@ 22 + + ArrowCursor + After an image is matched, this is the amount of time in millseconds that will be delayed before splitting. - - - - - 0 - - - 0.000000000000000 - - 36000000.000000000000000 - - - 1.000000000000000 - - - 0.000000000000000 + 999999999 @@ -430,7 +399,7 @@ 180 - 133 + 130 81 21 @@ -443,10 +412,13 @@ + + true + 76 - 28 + 30 94 20 @@ -481,7 +453,7 @@ 6 - 31 + 32 71 16 @@ -494,7 +466,7 @@ 76 - 53 + 55 94 20 @@ -532,7 +504,7 @@ 6 - 55 + 57 41 16 @@ -545,7 +517,7 @@ 180 - 53 + 55 81 21 @@ -590,7 +562,7 @@ 76 - 133 + 130 94 20 @@ -622,7 +594,7 @@ 180 - 106 + 105 81 21 @@ -638,7 +610,7 @@ 6 - 108 + 107 61 16 @@ -651,7 +623,7 @@ 76 - 106 + 105 94 20 @@ -672,46 +644,13 @@ skip_split_input pause_input default_comparison_method - default_similarity_threshold_double_spinbox - default_pause_time_double_spinbox + default_similarity_threshold_spinbox + default_pause_time_spinbox loop_splits_checkbox fps_limit_spinbox live_capture_region_checkbox force_print_window_checkbox - - - save_cancel_dialog_button_box - accepted() - dialog_settings - accept() - - - 194 - 541 - - - 140 - 282 - - - - - save_cancel_dialog_button_box - rejected() - dialog_settings - reject() - - - 194 - 541 - - - 140 - 282 - - - - + diff --git a/scripts/compile_resources.bat b/scripts/compile_resources.bat index 3c1398ff..d5ef6cf0 100644 --- a/scripts/compile_resources.bat +++ b/scripts/compile_resources.bat @@ -2,5 +2,6 @@ cd "%~dp0.." md .\src\gen pyuic6 ".\res\about.ui" -o ".\src\gen\about.py" pyuic6 ".\res\design.ui" -o ".\src\gen\design.py" +pyuic6 ".\res\settings.ui" -o ".\src\gen\settings.py" pyuic6 ".\res\update_checker.ui" -o ".\src\gen\update_checker.py" pyside6-rcc ".\res\resources.qrc" -o ".\src\gen\resources_rc.py" diff --git a/scripts/designer.bat b/scripts/designer.bat index 3b7af65e..d3b61064 100644 --- a/scripts/designer.bat +++ b/scripts/designer.bat @@ -11,4 +11,9 @@ IF NOT DEFINED PYTHONPATH ( SET PYTHONPATH=!pythonFiles[0]! ) -START "Qt Designer" "%PYTHONPATH:~0,-11%\Lib\site-packages\qt6_applications\Qt\bin\designer.exe" "%~d0%~p0..\res\design.ui" "%~d0%~p0..\res\about.ui" "%~d0%~p0..\res\update_checker.ui" +START "Qt Designer" "%PYTHONPATH:~0,-11%\Lib\site-packages\qt6_applications\Qt\bin\designer.exe"^ + "%~d0%~p0..\res\design.ui"^ + "%~d0%~p0..\res\about.ui"^ + "%~d0%~p0..\res\settings.ui"^ + "%~d0%~p0..\res\update_checker.ui" + diff --git a/src/AutoSplit.py b/src/AutoSplit.py index 97031748..6fd87522 100644 --- a/src/AutoSplit.py +++ b/src/AutoSplit.py @@ -28,18 +28,18 @@ import error_messages import settings_file as settings from AutoControlledWorker import AutoControlledWorker -from capture_windows import capture_region, Rect, set_ui_image -from gen import about, design, update_checker -from hotkeys import send_command, after_setting_hotkey, set_split_hotkey, set_reset_hotkey, set_skip_split_hotkey, \ - set_undo_split_hotkey, set_pause_hotkey -from menu_bar import open_about, VERSION, view_help, check_for_updates, open_update_checker +from capture_windows import capture_region, set_ui_image +from gen import about, design, settings as settings_ui, update_checker +from hotkeys import send_command, after_setting_hotkey +from menu_bar import get_default_settings_from_ui, open_about, VERSION, open_settings, view_help, check_for_updates, \ + open_update_checker from screen_region import select_region, select_window, align_region, validate_before_parsing from settings_file import FROZEN from split_parser import BELOW_FLAG, DUMMY_FLAG, PAUSE_FLAG, parse_and_validate_images -CREATE_NEW_ISSUE_MESSAGE = "Please create a New Issue at " \ - "github.com/Toufool/Auto-Split/issues, describe what happened, and copy & paste the error message below" -START_IMAGE_TEXT = "Start Image" +CREATE_NEW_ISSUE_MESSAGE = ( + "Please create a New Issue at " + + "github.com/Toufool/Auto-Split/issues, describe what happened, and copy & paste the error message below") START_AUTO_SPLITTER_TEXT = "Start Auto Splitter" CHECK_FPS_ITERATIONS = 10 @@ -47,14 +47,14 @@ os.environ["REQUESTS_CA_BUNDLE"] = certifi.where() -def make_excepthook(main_window: AutoSplit): +def make_excepthook(autosplit: AutoSplit): def excepthook(exception_type: type[BaseException], exception: BaseException, _traceback: Optional[TracebackType]): # Catch Keyboard Interrupts for a clean close if exception_type is KeyboardInterrupt or isinstance(exception, KeyboardInterrupt): sys.exit(0) - main_window.show_error_signal.emit(lambda: error_messages.exception_traceback( + autosplit.show_error_signal.emit(lambda: error_messages.exception_traceback( "AutoSplit encountered an unhandled exception and will try to recover, " - f"however, there is no guarantee everything will work properly. {CREATE_NEW_ISSUE_MESSAGE}", + + f"however, there is no guarantee everything will work properly. {CREATE_NEW_ISSUE_MESSAGE}", exception)) return excepthook @@ -82,9 +82,10 @@ class AutoSplit(QMainWindow, design.Ui_MainWindow): timer_start_image = QtCore.QTimer() # Widgets - AboutWidget: about.Ui_AboutAutoSplitWidget - UpdateCheckerWidget: update_checker.Ui_UpdateChecker - CheckForUpdatesThread: QtCore.QThread + AboutWidget: Optional[about.Ui_AboutAutoSplitWidget] = None + UpdateCheckerWidget: Optional[update_checker.Ui_UpdateChecker] = None + CheckForUpdatesThread: Optional[QtCore.QThread] = None + SettingsWidget: Optional[settings_ui.Ui_DialogSettings] = None # hotkeys need to be initialized to be passed as thread arguments in hotkeys.py # and for type safety in both hotkeys.py and settings_file.py @@ -95,13 +96,10 @@ class AutoSplit(QMainWindow, design.Ui_MainWindow): pause_hotkey: Optional[Callable[[], None]] = None # Initialize a few attributes - split_image_directory = "" hwnd = 0 """Window Handle used for Capture Region""" - window_text = "" - selection = Rect() last_saved_settings: list[Union[str, float, int, bool]] = [] - live_image_function_on_open = True + similarity = 0.0 split_image_number = 0 split_images_and_loop_number: list[tuple[AutoSplitImage, int]] = [] split_groups: list[list[int]] = [] @@ -130,34 +128,32 @@ def __init__(self, parent: Optional[QWidget] = None): # Setup global error handling self.show_error_signal.connect(lambda errorMessageBox: errorMessageBox()) - # Whithin LiveSplit excepthook needs to use main_window's signals to show errors + # Whithin LiveSplit excepthook needs to use MainWindow's signals to show errors sys.excepthook = make_excepthook(self) self.setupUi(self) + # Get default values defined in SettingsDialog + self.settings_dict = get_default_settings_from_ui(self) settings.load_check_for_updates_on_open(self) - # close all processes when closing window self.action_view_help.triggered.connect(view_help) self.action_about.triggered.connect(lambda: open_about(self)) self.action_check_for_updates.triggered.connect(lambda: check_for_updates(self)) - self.action_save_settings.triggered.connect(lambda: settings.save_settings(self)) - self.action_save_settings_as.triggered.connect(lambda: settings.save_settings_as(self)) - self.action_load_settings.triggered.connect(lambda: settings.load_settings(self)) + self.action_settings.triggered.connect(lambda: open_settings(self)) + self.action_save_profile.triggered.connect(lambda: settings.save_settings(self)) + self.action_save_profile_as.triggered.connect(lambda: settings.save_settings_as(self)) + self.action_load_profile.triggered.connect(lambda: settings.load_settings(self)) + + if self.SettingsWidget: + self.SettingsWidget.split_input.setEnabled(False) + self.SettingsWidget.reset_input.setEnabled(False) + self.SettingsWidget.skip_split_input.setEnabled(False) + self.SettingsWidget.undo_split_input.setEnabled(False) + self.SettingsWidget.pause_input.setEnabled(False) if self.is_auto_controlled: - self.set_split_hotkey_button.setEnabled(False) - self.set_reset_hotkey_button.setEnabled(False) - self.set_skip_split_hotkey_button.setEnabled(False) - self.set_undo_split_hotkey_button.setEnabled(False) - self.set_pause_hotkey_button.setEnabled(False) self.start_auto_splitter_button.setEnabled(False) - self.split_input.setEnabled(False) - self.reset_input.setEnabled(False) - self.skip_split_input.setEnabled(False) - self.undo_split_input.setEnabled(False) - self.pause_input.setEnabled(False) - self.timer_global_hotkeys_label.setText("Hotkeys Inactive - Use LiveSplit Hotkeys") # Send version and process ID to stdout print(f"{VERSION}\n{os.getpid()}", flush=True) @@ -183,14 +179,9 @@ def __init__(self, parent: Optional[QWidget] = None): self.undo_split_button.clicked.connect(self.__undo_split) self.next_image_button.clicked.connect(lambda: self.__skip_split(True)) self.previous_image_button.clicked.connect(lambda: self.__undo_split(True)) - self.set_split_hotkey_button.clicked.connect(lambda: set_split_hotkey(self)) - self.set_reset_hotkey_button.clicked.connect(lambda: set_reset_hotkey(self)) - self.set_skip_split_hotkey_button.clicked.connect(lambda: set_skip_split_hotkey(self)) - self.set_undo_split_hotkey_button.clicked.connect(lambda: set_undo_split_hotkey(self)) - self.set_pause_hotkey_button.clicked.connect(lambda: set_pause_hotkey(self)) self.align_region_button.clicked.connect(lambda: align_region(self)) self.select_window_button.clicked.connect(lambda: select_window(self)) - self.start_image_reload_button.clicked.connect(lambda: self.load_start_image(True, True)) + self.reload_start_image_button.clicked.connect(lambda: self.load_start_image(True, True)) self.action_check_for_updates_on_open.changed.connect(lambda: settings.set_check_for_updates_on_open( self, self.action_check_for_updates_on_open.isChecked()) @@ -213,7 +204,7 @@ def __init__(self, parent: Optional[QWidget] = None): self.pause_signal.connect(self.pause) # live image checkbox - self.live_image_checkbox.clicked.connect(self.check_live_image) + self.timer_live_image.start(int(1000 / 60)) self.timer_live_image.timeout.connect(self.__live_image_function) # Automatic timer start @@ -236,49 +227,37 @@ def __browse(self): new_split_image_directory = QFileDialog.getExistingDirectory( self, "Select Split Image Directory", - os.path.join(self.split_image_directory or settings.auto_split_directory, "..")) + os.path.join(self.settings_dict["split_image_directory"] or settings.auto_split_directory, "..")) # If the user doesn't select a folder, it defaults to "". if new_split_image_directory: # set the split image folder line to the directory text - self.split_image_directory = new_split_image_directory + self.settings_dict["split_image_directory"] = new_split_image_directory self.split_image_folder_input.setText(f"{new_split_image_directory}/") self.load_start_image() - def check_live_image(self): - if self.live_image_checkbox.isChecked(): - self.timer_live_image.start(int(1000 / 60)) - else: - self.timer_live_image.stop() - self.__live_image_function() - def __live_image_function(self): - try: - self.capture_region_window_label.setText(self.window_text) - if not self.window_text: - self.timer_live_image.stop() - self.live_image.clear() - if self.live_image_function_on_open: - self.live_image_function_on_open = False - return - # Set live image in UI - if self.hwnd: - capture = capture_region(self.hwnd, self.selection, self.force_print_window_checkbox.isChecked()) - set_ui_image(self.live_image, capture, False) - - except AttributeError: - pass + self.capture_region_window_label.setText(self.settings_dict["captured_window_title"]) + if not (self.settings_dict["live_capture_region"] and self.settings_dict["captured_window_title"]): + self.live_image.clear() + return + # Set live image in UI + if self.hwnd: + capture = capture_region(self.hwnd, + self.settings_dict["capture_region"], + self.settings_dict["force_print_window"]) + set_ui_image(self.live_image, capture, False) def load_start_image(self, started_by_button: bool = False, wait_for_delay: bool = True): self.timer_start_image.stop() - self.current_split_image_file_label.setText(" ") - self.start_image_label.setText(f"{START_IMAGE_TEXT}: not found") + self.current_image_file_label.setText("-") + self.start_image_status_value_label.setText("not found") QApplication.processEvents() if not self.is_auto_controlled \ - and (not self.split_input.text() - or not self.reset_input.text() - or not self.pause_input.text()): + and (not self.settings_dict["split_hotkey"] + or not self.settings_dict["reset_hotkey"] + or not self.settings_dict["pause_hotkey"]): error_messages.load_start_image() return @@ -295,17 +274,17 @@ def load_start_image(self, started_by_button: bool = False, wait_for_delay: bool start_pause_time = self.start_image.get_pause_time(self) if not wait_for_delay and start_pause_time > 0: self.check_start_image_timestamp = time() + start_pause_time - self.start_image_label.setText(f"{START_IMAGE_TEXT}: paused") - self.highest_similarity_label.setText(" ") - self.current_similarity_threshold_number_label.setText(" ") + self.start_image_status_value_label.setText("paused") + self.table_current_image_highest_label.setText("-") + self.table_current_image_threshold_label.setText("-") else: self.check_start_image_timestamp = 0.0 - self.start_image_label.setText(f"{START_IMAGE_TEXT}: ready") + self.start_image_status_value_label.setText("ready") self.__update_split_image(self.start_image) self.highest_similarity = 0.0 self.start_image_split_below_threshold = False - self.timer_start_image.start(int(1000 / self.fps_limit_spinbox.value())) + self.timer_start_image.start(int(1000 / self.settings_dict["fps_limit"])) QApplication.processEvents() @@ -313,35 +292,31 @@ def __start_image_function(self): if self.start_image is None \ or not self.start_image \ or time() < self.check_start_image_timestamp \ - or (not self.split_input.text() and not self.is_auto_controlled): + or (not self.settings_dict["split_hotkey"] and not self.is_auto_controlled): pause_time_left = f"{self.check_start_image_timestamp - time():.1f}" self.current_split_image.setText( - f"None\n (Paused before loading {START_IMAGE_TEXT}).\n {pause_time_left} sec remaining") + f"None\n (Paused before loading Start Image).\n {pause_time_left} sec remaining") return if self.check_start_image_timestamp > 0: self.check_start_image_timestamp = 0.0 - self.start_image_label.setText(f"{START_IMAGE_TEXT}: ready") + self.start_image_status_value_label.setText("ready") self.__update_split_image(self.start_image) capture = self.__get_capture_for_comparison() start_image_threshold = self.start_image.get_similarity_threshold(self) start_image_similarity = self.start_image.compare_with_capture(self, capture) - self.current_similarity_threshold_number_label.setText(f"{start_image_threshold:.2f}") + self.table_current_image_threshold_label.setText(f"{start_image_threshold:.2f}") # Show live similarity if the checkbox is checked - self.live_similarity_label.setText(str(start_image_similarity)[:4] - if self.show_live_similarity_checkbox.isChecked() - else " ") + self.table_current_image_live_label.setText(str(start_image_similarity)[:4]) # If the similarity becomes higher than highest similarity, set it as such. if start_image_similarity > self.highest_similarity: self.highest_similarity = start_image_similarity # Show live highest similarity if the checkbox is checked - self.highest_similarity_label.setText(str(self.highest_similarity)[:4] - if self.show_highest_similarity_checkbox.isChecked() - else " ") + self.table_current_image_highest_label.setText(str(self.highest_similarity)[:4]) # If the {b} flag is set, let similarity go above threshold first, then split on similarity below threshold # Otherwise just split when similarity goes above threshold @@ -361,7 +336,7 @@ def __start_image_function(self): # delay start image if needed if self.start_image.delay > 0: - self.start_image_label.setText(f"{START_IMAGE_TEXT}: delaying start...") + self.start_image_status_value_label.setText("delaying start...") delay_start_time = time() start_delay = self.start_image.delay / 1000 while time() - delay_start_time < start_delay: @@ -371,36 +346,24 @@ def __start_image_function(self): # Email sent to pyqt@riverbankcomputing.com QtTest.QTest.qWait(1) # type: ignore - self.start_image_label.setText(f"{START_IMAGE_TEXT}: started") + self.start_image_status_value_label.setText("started") send_command(self, "start") # Email sent to pyqt@riverbankcomputing.com - QtTest.QTest.qWait(int(1 / self.fps_limit_spinbox.value())) # type: ignore + QtTest.QTest.qWait(int(1 / self.settings_dict["fps_limit"])) # type: ignore self.start_auto_splitter() # update x, y, width, height when spinbox values are changed def __update_x(self): - try: - self.selection.left = self.x_spinbox.value() - self.selection.right = self.selection.left + self.width_spinbox.value() - self.check_live_image() - except AttributeError: - pass + self.settings_dict["capture_region"].x = self.x_spinbox.value() def __update_y(self): - try: - self.selection.top = self.y_spinbox.value() - self.selection.bottom = self.selection.top + self.height_spinbox.value() - self.check_live_image() - except AttributeError: - pass + self.settings_dict["capture_region"].y = self.y_spinbox.value() def __update_width(self): - self.selection.right = self.selection.left + self.width_spinbox.value() - self.check_live_image() + self.settings_dict["capture_region"].width = self.width_spinbox.value() def __update_height(self): - self.selection.bottom = self.selection.top + self.height_spinbox.value() - self.check_live_image() + self.settings_dict["capture_region"].height = self.height_spinbox.value() def __take_screenshot(self): if not validate_before_parsing(self, check_empty_directory=False): @@ -411,13 +374,17 @@ def __take_screenshot(self): # which is a problem, but I doubt anyone will get to 1000 split images... screenshot_index = 1 while True: - screenshot_path = os.path.join(self.split_image_directory, f"{screenshot_index:03}_SplitImage.png") + screenshot_path = os.path.join( + self.settings_dict["split_image_directory"], + f"{screenshot_index:03}_SplitImage.png") if not os.path.exists(screenshot_path): break screenshot_index += 1 # Grab screenshot of capture region - capture = capture_region(self.hwnd, self.selection, self.force_print_window_checkbox.isChecked()) + capture = capture_region(self.hwnd, + self.settings_dict["capture_region"], + self.settings_dict["force_print_window"]) if capture is None: error_messages.region() return @@ -427,7 +394,7 @@ def __take_screenshot(self): os.startfile(screenshot_path) def __check_fps(self): - self.fps_value_label.setText(" ") + self.fps_value_label.clear() if not (validate_before_parsing(self) and parse_and_validate_images(self)): return @@ -521,15 +488,15 @@ def start_auto_splitter(self): or (not self.start_auto_splitter_button.isEnabled() and not self.is_auto_controlled): return - start_label: str = self.start_image_label.text() + start_label: str = self.start_image_status_value_label.text() if start_label.endswith("ready") or start_label.endswith("paused"): - self.start_image_label.setText(f"{START_IMAGE_TEXT}: not ready") + self.start_image_status_value_label.setText("not ready") self.start_auto_splitter_signal.emit() def __check_for_reset(self): if self.start_auto_splitter_button.text() == START_AUTO_SPLITTER_TEXT: - if self.auto_start_on_reset_checkbox.isChecked(): + if self.settings_dict["loop_splits"]: self.start_auto_splitter_signal.emit() else: self.gui_changes_on_reset() @@ -537,7 +504,7 @@ def __check_for_reset(self): return False def __auto_splitter(self): - if not self.split_input.text() and not self.is_auto_controlled: + if not self.settings_dict["split_hotkey"] and not self.is_auto_controlled: self.gui_changes_on_reset() error_messages.split_hotkey() return @@ -606,20 +573,14 @@ def __auto_splitter(self): self.similarity = self.split_image.compare_with_capture(self, capture) # show live similarity if the checkbox is checked - self.live_similarity_label.setText( - str(self.similarity)[:4] - if self.show_live_similarity_checkbox.isChecked() - else " ") + self.table_current_image_live_label.setText(str(self.similarity)[:4]) # if the similarity becomes higher than highest similarity, set it as such. if self.similarity > self.highest_similarity: self.highest_similarity = self.similarity # show live highest similarity if the checkbox is checked - self.highest_similarity_label.setText( - str(self.highest_similarity)[:4] - if self.show_highest_similarity_checkbox.isChecked() - else " ") + self.table_current_image_highest_label.setText(str(self.highest_similarity)[:4]) # If its the last split image and last loop number, disable the next image button # If its the first split image, disable the undo split and previous image buttons @@ -645,7 +606,7 @@ def __auto_splitter(self): break # limit the number of time the comparison runs to reduce cpu usage - frame_interval: float = 1 / self.fps_limit_spinbox.value() + frame_interval: float = 1 / self.settings_dict["fps_limit"] # Email sent to pyqt@riverbankcomputing.com QtTest.QTest.qWait(int(frame_interval - (time() - start) % frame_interval)) # type: ignore QApplication.processEvents() @@ -663,7 +624,7 @@ def __auto_splitter(self): self.waiting_for_split_delay = True self.undo_split_button.setEnabled(False) self.skip_split_button.setEnabled(False) - self.current_split_image_file_label.setText(" ") + self.current_image_file_label.clear() # check for reset while delayed and display a counter of the remaining split delay time delay_start_time = time() @@ -687,7 +648,7 @@ def __auto_splitter(self): # if loop check box is checked and its the last split, go to first split. # else go to the next split image. - if self.loop_checkbox.isChecked() and self.split_image_number == number_of_split_images - 1: + if self.settings_dict["loop_splits"] and self.split_image_number == number_of_split_images - 1: self.split_image_number = 0 else: self.split_image_number += 1 @@ -742,46 +703,49 @@ def gui_changes_on_start(self): self.timer_start_image.stop() self.start_auto_splitter_button.setText("Running...") self.browse_button.setEnabled(False) - self.start_image_reload_button.setEnabled(False) + self.reload_start_image_button.setEnabled(False) self.previous_image_button.setEnabled(True) self.next_image_button.setEnabled(True) + if self.SettingsWidget: + self.SettingsWidget.set_split_hotkey_button.setEnabled(False) + self.SettingsWidget.set_reset_hotkey_button.setEnabled(False) + self.SettingsWidget.set_skip_split_hotkey_button.setEnabled(False) + self.SettingsWidget.set_undo_split_hotkey_button.setEnabled(False) + self.SettingsWidget.set_pause_hotkey_button.setEnabled(False) + if not self.is_auto_controlled: self.start_auto_splitter_button.setEnabled(False) self.reset_button.setEnabled(True) self.undo_split_button.setEnabled(True) self.skip_split_button.setEnabled(True) - self.set_split_hotkey_button.setEnabled(False) - self.set_reset_hotkey_button.setEnabled(False) - self.set_skip_split_hotkey_button.setEnabled(False) - self.set_undo_split_hotkey_button.setEnabled(False) - self.set_pause_hotkey_button.setEnabled(False) QApplication.processEvents() def gui_changes_on_reset(self): self.start_auto_splitter_button.setText(START_AUTO_SPLITTER_TEXT) - self.image_loop_label.setText("Image Loop: -") - self.current_split_image.setText(" ") - self.current_split_image_file_label.setText(" ") - self.live_similarity_label.setText(" ") - self.highest_similarity_label.setText(" ") - self.current_similarity_threshold_number_label.setText(" ") + self.image_loop_value_label.setText("N/A") + self.current_split_image.clear() + self.current_image_file_label.clear() + self.table_current_image_live_label.setText("-") + self.table_current_image_highest_label.setText("-") + self.table_current_image_threshold_label.setText("-") self.browse_button.setEnabled(True) - self.start_image_reload_button.setEnabled(True) + self.reload_start_image_button.setEnabled(True) self.previous_image_button.setEnabled(False) self.next_image_button.setEnabled(False) + if self.SettingsWidget: + self.SettingsWidget.set_split_hotkey_button.setEnabled(True) + self.SettingsWidget.set_reset_hotkey_button.setEnabled(True) + self.SettingsWidget.set_skip_split_hotkey_button.setEnabled(True) + self.SettingsWidget.set_undo_split_hotkey_button.setEnabled(True) + self.SettingsWidget.set_pause_hotkey_button.setEnabled(True) if not self.is_auto_controlled: self.start_auto_splitter_button.setEnabled(True) self.reset_button.setEnabled(False) self.undo_split_button.setEnabled(False) self.skip_split_button.setEnabled(False) - self.set_split_hotkey_button.setEnabled(True) - self.set_reset_hotkey_button.setEnabled(True) - self.set_skip_split_hotkey_button.setEnabled(True) - self.set_undo_split_hotkey_button.setEnabled(True) - self.set_pause_hotkey_button.setEnabled(True) QApplication.processEvents() self.load_start_image(False, False) @@ -790,18 +754,22 @@ def __get_capture_for_comparison(self): """ Grab capture region and resize for comparison """ - capture = capture_region(self.hwnd, self.selection, self.force_print_window_checkbox.isChecked()) + capture = capture_region(self.hwnd, + self.settings_dict["capture_region"], + self.settings_dict["force_print_window"]) # This most likely means we lost capture (ie the captured window was closed, crashed, etc.) if capture is None: # Try to recover by using the window name self.live_image.setText("Trying to recover window...") # https://github.com/kaluluosi/pywin32-stubs/issues/7 - hwnd = win32gui.FindWindow(None, self.window_text) # type: ignore + hwnd = win32gui.FindWindow(None, self.settings_dict["captured_window_title"]) # type: ignore # Don't fallback to desktop if hwnd: self.hwnd = hwnd - capture = capture_region(self.hwnd, self.selection, self.force_print_window_checkbox.isChecked()) + capture = capture_region(self.hwnd, + self.settings_dict["capture_region"], + self.settings_dict["force_print_window"]) return None if capture is None else cv2.resize(capture, COMPARISON_RESIZE, interpolation=cv2.INTER_NEAREST) def __reset_if_should(self, capture: Optional[cv2.ndarray]): @@ -833,15 +801,15 @@ def __update_split_image(self, specific_image: Optional[AutoSplitImage] = None): if self.split_image.bytes is not None: set_ui_image(self.current_split_image, self.split_image.bytes, True) - self.current_split_image_file_label.setText(self.split_image.filename) - self.current_similarity_threshold_number_label.setText(f"{self.split_image.get_similarity_threshold(self):.2f}") + self.current_image_file_label.setText(self.split_image.filename) + self.table_current_image_threshold_label.setText(f"{self.split_image.get_similarity_threshold(self):.2f}") # Set Image Loop # if specific_image and specific_image.image_type == ImageType.START: - self.image_loop_label.setText("Image Loop: N/A") + self.image_loop_value_label.setText("N/A") else: loop_tuple = self.split_images_and_loop_number[self.split_image_number] - self.image_loop_label.setText(f"Image Loop: {loop_tuple[1]}/{loop_tuple[0].loops}") + self.image_loop_value_label.setText(f"{loop_tuple[1]}/{loop_tuple[0].loops}") self.highest_similarity = 0.0 # need to set split below threshold to false each time an image updates. diff --git a/src/AutoSplitImage.py b/src/AutoSplitImage.py index 126f2abe..9eeaf8a9 100644 --- a/src/AutoSplitImage.py +++ b/src/AutoSplitImage.py @@ -46,7 +46,7 @@ def get_pause_time(self, default: Union[AutoSplit, float]): """ default_value: float = default \ if isinstance(default, float) \ - else default.pause_spinbox.value() + else default.settings_dict["default_pause_time"] return default_value if self.__pause_time is None else self.__pause_time def get_similarity_threshold(self, default: Union[AutoSplit, float]): @@ -55,7 +55,7 @@ def get_similarity_threshold(self, default: Union[AutoSplit, float]): """ default_value: float = default \ if isinstance(default, float) \ - else default.similarity_threshold_spinbox.value() + else default.settings_dict["default_similarity_threshold"] return default_value if self.__similarity_threshold is None else self.__similarity_threshold def __init__(self, path: str): @@ -109,7 +109,7 @@ def compare_with_capture( """ comparison_method: int = comparison \ if isinstance(comparison, int) \ - else comparison.comparison_method_combobox.currentIndex() + else comparison.settings_dict["default_comparison_method"] if self.bytes is None or capture is None: return 0.0 diff --git a/src/capture_windows.py b/src/capture_windows.py index c991cc38..c6401efa 100644 --- a/src/capture_windows.py +++ b/src/capture_windows.py @@ -20,17 +20,15 @@ @dataclass -class Rect(ctypes.wintypes.RECT): - """ - Overrides `ctypes.wintypes.RECT` to replace c_long with int for math operators - """ - left: int = -1 # type: ignore - top: int = -1 # type: ignore - right: int = -1 # type: ignore - bottom: int = -1 # type: ignore +class Region(): + def __init__(self, x: int, y: int, width: int, height: int): + self.x = x + self.y = y + self.width = width + self.height = height -def capture_region(hwnd: int, selection: Rect, print_window: bool): +def capture_region(hwnd: int, selection: Region, print_window: bool): """ Captures an image of the region for a window matching the given parameters of the bounding box @@ -40,8 +38,6 @@ def capture_region(hwnd: int, selection: Rect, print_window: bool): @return: The image of the region in the window in BGRA format """ - width: int = selection.right - selection.left - height: int = selection.bottom - selection.top # If the window closes while it's being manipulated, it could cause a crash try: window_dc: int = win32gui.GetWindowDC(hwnd) @@ -54,16 +50,17 @@ def capture_region(hwnd: int, selection: Rect, print_window: bool): compatible_dc = cast(PyCDC, dc_object.CreateCompatibleDC()) bitmap: PyCBitmap = win32ui.CreateBitmap() - bitmap.CreateCompatibleBitmap(dc_object, width, height) + bitmap.CreateCompatibleBitmap(dc_object, selection.width, selection.height) compatible_dc.SelectObject(bitmap) - compatible_dc.BitBlt((0, 0), (width, height), dc_object, (selection.left, selection.top), win32con.SRCCOPY) + compatible_dc.BitBlt((0, 0), (selection.width, selection.height), dc_object, + (selection.x, selection.y), win32con.SRCCOPY) # https://github.com/kaluluosi/pywin32-stubs/issues/5 # pylint: disable=no-member except (win32ui.error, pywintypes.error): # type: ignore return None image = np.frombuffer(cast(bytes, bitmap.GetBitmapBits(True)), dtype="uint8") - image.shape = (height, width, 4) + image.shape = (selection.height, selection.width, 4) try: dc_object.DeleteDC() diff --git a/src/error_messages.py b/src/error_messages.py index 5d8bd382..cb437f36 100644 --- a/src/error_messages.py +++ b/src/error_messages.py @@ -31,12 +31,12 @@ def split_image_directory_empty(): def image_type(image: str): set_text_message(f'"{image}" is not a valid image file, does not exist, ' - "or the full image file path contains a special character.") + + "or the full image file path contains a special character.") def region(): set_text_message("No region is selected or the Capture Region window is not open. " - "Select a region or load settings while the Capture Region window is open.") + + "Select a region or load settings while the Capture Region window is open.") def split_hotkey(): @@ -45,7 +45,7 @@ def split_hotkey(): def pause_hotkey(): set_text_message("Your split image folder contains an image filename with a pause flag {p}, " - "but no pause hotkey is set.") + + "but no pause hotkey is set.") def align_region_image_type(): @@ -82,7 +82,7 @@ def no_settings_file_on_open(): def too_many_settings_files_on_open(): set_text_message("Too many settings files found. " - "Only one can be loaded on open if placed in the same folder as AutoSplit.exe") + + "Only one can be loaded on open if placed in the same folder as AutoSplit.exe") def check_for_updates(): @@ -91,7 +91,7 @@ def check_for_updates(): def load_start_image(): set_text_message("Start Image found, but cannot be loaded unless Start, Reset, and Pause hotkeys are set. " - "Please set these hotkeys, and then click the Reload Start Image button.") + + "Please set these hotkeys, and then click the Reload Start Image button.") def stdin_lost(): diff --git a/src/hotkeys.py b/src/hotkeys.py index 80701127..3a6901e2 100644 --- a/src/hotkeys.py +++ b/src/hotkeys.py @@ -1,6 +1,7 @@ from __future__ import annotations from typing import Literal, Optional, TYPE_CHECKING, Union from collections.abc import Callable + if TYPE_CHECKING: from AutoSplit import AutoSplit @@ -18,27 +19,29 @@ # do all of these after you click "Set Hotkey" but before you type the hotkey. def before_setting_hotkey(autosplit: AutoSplit): autosplit.start_auto_splitter_button.setEnabled(False) - autosplit.set_split_hotkey_button.setEnabled(False) - autosplit.set_reset_hotkey_button.setEnabled(False) - autosplit.set_skip_split_hotkey_button.setEnabled(False) - autosplit.set_undo_split_hotkey_button.setEnabled(False) - autosplit.set_pause_hotkey_button.setEnabled(False) + if autosplit.SettingsWidget: + autosplit.SettingsWidget.set_split_hotkey_button.setEnabled(False) + autosplit.SettingsWidget.set_reset_hotkey_button.setEnabled(False) + autosplit.SettingsWidget.set_skip_split_hotkey_button.setEnabled(False) + autosplit.SettingsWidget.set_undo_split_hotkey_button.setEnabled(False) + autosplit.SettingsWidget.set_pause_hotkey_button.setEnabled(False) # do all of these things after you set a hotkey. a signal connects to this because # changing GUI stuff in the hotkey thread was causing problems def after_setting_hotkey(autosplit: AutoSplit): - autosplit.set_split_hotkey_button.setText(SET_HOTKEY_TEXT) - autosplit.set_reset_hotkey_button.setText(SET_HOTKEY_TEXT) - autosplit.set_skip_split_hotkey_button.setText(SET_HOTKEY_TEXT) - autosplit.set_undo_split_hotkey_button.setText(SET_HOTKEY_TEXT) - autosplit.set_pause_hotkey_button.setText(SET_HOTKEY_TEXT) autosplit.start_auto_splitter_button.setEnabled(True) - autosplit.set_split_hotkey_button.setEnabled(True) - autosplit.set_reset_hotkey_button.setEnabled(True) - autosplit.set_skip_split_hotkey_button.setEnabled(True) - autosplit.set_undo_split_hotkey_button.setEnabled(True) - autosplit.set_pause_hotkey_button.setEnabled(True) + if autosplit.SettingsWidget: + autosplit.SettingsWidget.set_split_hotkey_button.setText(SET_HOTKEY_TEXT) + autosplit.SettingsWidget.set_reset_hotkey_button.setText(SET_HOTKEY_TEXT) + autosplit.SettingsWidget.set_skip_split_hotkey_button.setText(SET_HOTKEY_TEXT) + autosplit.SettingsWidget.set_undo_split_hotkey_button.setText(SET_HOTKEY_TEXT) + autosplit.SettingsWidget.set_pause_hotkey_button.setText(SET_HOTKEY_TEXT) + autosplit.SettingsWidget.set_split_hotkey_button.setEnabled(True) + autosplit.SettingsWidget.set_reset_hotkey_button.setEnabled(True) + autosplit.SettingsWidget.set_skip_split_hotkey_button.setEnabled(True) + autosplit.SettingsWidget.set_undo_split_hotkey_button.setEnabled(True) + autosplit.SettingsWidget.set_pause_hotkey_button.setEnabled(True) def is_digit(key: Optional[str]): @@ -57,15 +60,15 @@ def send_command(autosplit: AutoSplit, command: Commands): if autosplit.is_auto_controlled: print(command, flush=True) elif command in {"split", "start"}: - _send_hotkey(autosplit.split_input.text()) + _send_hotkey(autosplit.settings_dict["split_hotkey"]) elif command == "pause": - _send_hotkey(autosplit.pause_input.text()) + _send_hotkey(autosplit.settings_dict["pause_hotkey"]) elif command == "reset": - _send_hotkey(autosplit.reset_input.text()) + _send_hotkey(autosplit.settings_dict["reset_hotkey"]) elif command == "skip": - _send_hotkey(autosplit.skip_split_input.text()) + _send_hotkey(autosplit.settings_dict["skip_split_hotkey"]) elif command == "undo": - _send_hotkey(autosplit.undo_split_input.text()) + _send_hotkey(autosplit.settings_dict["undo_split_hotkey"]) else: raise KeyError(f"'{command}' is not a valid LiveSplit.AutoSplitIntegration command") @@ -138,18 +141,19 @@ def __get_key_name(keyboard_event: KeyboardEvent): def __is_key_already_set(autosplit: AutoSplit, key_name: str): - return key_name in (autosplit.split_input.text(), - autosplit.reset_input.text(), - autosplit.skip_split_input.text(), - autosplit.undo_split_input.text(), - autosplit.pause_input.text()) + return key_name in (autosplit.settings_dict["split_hotkey"], + autosplit.settings_dict["reset_hotkey"], + autosplit.settings_dict["skip_split_hotkey"], + autosplit.settings_dict["undo_split_hotkey"], + autosplit.settings_dict["pause_hotkey"]) # --------------------HOTKEYS-------------------------- # TODO: Refactor to de-duplicate all this code, including settings_file.py # Going to comment on one func, and others will be similar. def set_split_hotkey(autosplit: AutoSplit, preselected_key: str = ""): - autosplit.set_split_hotkey_button.setText(PRESS_A_KEY_TEXT) + if autosplit.SettingsWidget: + autosplit.SettingsWidget.set_split_hotkey_button.setText(PRESS_A_KEY_TEXT) # disable some buttons before_setting_hotkey(autosplit) @@ -188,7 +192,9 @@ def callback(): autosplit.split_hotkey = keyboard.hook_key( key_name, lambda error: _hotkey_action(error, key_name, autosplit.start_auto_splitter)) - autosplit.split_input.setText(key_name) + if autosplit.SettingsWidget: + autosplit.SettingsWidget.split_input.setText(key_name) + autosplit.settings_dict["split_hotkey"] = key_name autosplit.after_setting_hotkey_signal.emit() # try to remove the previously set hotkey if there is one. @@ -198,7 +204,8 @@ def callback(): def set_reset_hotkey(autosplit: AutoSplit, preselected_key: str = ""): - autosplit.set_reset_hotkey_button.setText(PRESS_A_KEY_TEXT) + if autosplit.SettingsWidget: + autosplit.SettingsWidget.set_reset_hotkey_button.setText(PRESS_A_KEY_TEXT) before_setting_hotkey(autosplit) def callback(): @@ -215,7 +222,9 @@ def callback(): autosplit.reset_hotkey = keyboard.hook_key( key_name, lambda error: _hotkey_action(error, key_name, autosplit.reset_signal.emit)) - autosplit.reset_input.setText(key_name) + if autosplit.SettingsWidget: + autosplit.SettingsWidget.reset_input.setText(key_name) + autosplit.settings_dict["reset_hotkey"] = key_name autosplit.after_setting_hotkey_signal.emit() _unhook(autosplit.reset_hotkey) @@ -224,7 +233,8 @@ def callback(): def set_skip_split_hotkey(autosplit: AutoSplit, preselected_key: str = ""): - autosplit.set_skip_split_hotkey_button.setText(PRESS_A_KEY_TEXT) + if autosplit.SettingsWidget: + autosplit.SettingsWidget.set_skip_split_hotkey_button.setText(PRESS_A_KEY_TEXT) before_setting_hotkey(autosplit) def callback(): @@ -241,7 +251,9 @@ def callback(): autosplit.skip_split_hotkey = keyboard.hook_key( key_name, lambda error: _hotkey_action(error, key_name, autosplit.skip_split_signal.emit)) - autosplit.skip_split_input.setText(key_name) + if autosplit.SettingsWidget: + autosplit.SettingsWidget.skip_split_input.setText(key_name) + autosplit.settings_dict["skip_split_hotkey"] = key_name autosplit.after_setting_hotkey_signal.emit() _unhook(autosplit.skip_split_hotkey) @@ -250,7 +262,8 @@ def callback(): def set_undo_split_hotkey(autosplit: AutoSplit, preselected_key: str = ""): - autosplit.set_undo_split_hotkey_button.setText(PRESS_A_KEY_TEXT) + if autosplit.SettingsWidget: + autosplit.SettingsWidget.set_undo_split_hotkey_button.setText(PRESS_A_KEY_TEXT) before_setting_hotkey(autosplit) def callback(): @@ -267,7 +280,9 @@ def callback(): autosplit.undo_split_hotkey = keyboard.hook_key( key_name, lambda error: _hotkey_action(error, key_name, autosplit.undo_split_signal.emit)) - autosplit.undo_split_input.setText(key_name) + if autosplit.SettingsWidget: + autosplit.SettingsWidget.undo_split_input.setText(key_name) + autosplit.settings_dict["undo_split_hotkey"] = key_name autosplit.after_setting_hotkey_signal.emit() _unhook(autosplit.undo_split_hotkey) @@ -276,7 +291,8 @@ def callback(): def set_pause_hotkey(autosplit: AutoSplit, preselected_key: str = ""): - autosplit.set_pause_hotkey_button.setText(PRESS_A_KEY_TEXT) + if autosplit.SettingsWidget: + autosplit.SettingsWidget.set_pause_hotkey_button.setText(PRESS_A_KEY_TEXT) before_setting_hotkey(autosplit) def callback(): @@ -293,7 +309,9 @@ def callback(): autosplit.pause_hotkey = keyboard.hook_key( key_name, lambda error: _hotkey_action(error, key_name, autosplit.pause_signal.emit)) - autosplit.pause_input.setText(key_name) + if autosplit.SettingsWidget: + autosplit.SettingsWidget.pause_input.setText(key_name) + autosplit.settings_dict["pause_hotkey"] = key_name autosplit.after_setting_hotkey_signal.emit() _unhook(autosplit.pause_hotkey) diff --git a/src/menu_bar.py b/src/menu_bar.py index 66cb9c85..d90c1e8c 100644 --- a/src/menu_bar.py +++ b/src/menu_bar.py @@ -1,5 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any + if TYPE_CHECKING: from AutoSplit import AutoSplit @@ -14,7 +15,9 @@ import error_messages import settings_file as settings -from gen import about, design, resources_rc, update_checker # noqa: F401 +from capture_windows import Region +from gen import about, design, resources_rc, settings as settings_ui, update_checker # noqa: F401 +from hotkeys import set_split_hotkey, set_reset_hotkey, set_skip_split_hotkey, set_undo_split_hotkey, set_pause_hotkey # AutoSplit Version number VERSION = "1.6.1" @@ -82,7 +85,7 @@ def __init__(self, autosplit: AutoSplit, check_on_open: bool): def run(self): try: response = requests.get("https://api.github.com/repos/Toufool/Auto-Split/releases/latest") - latest_version = response.json()["name"].split("v")[1] + latest_version = str(response.json()["name"]).split("v")[1] self.autosplit.update_checker_widget_signal.emit(latest_version, self.check_on_open) except (RequestException, KeyError, JSONDecodeError): if not self.check_on_open: @@ -92,3 +95,107 @@ def run(self): def check_for_updates(autosplit: AutoSplit, check_on_open: bool = False): autosplit.CheckForUpdatesThread = __CheckForUpdatesThread(autosplit, check_on_open) autosplit.CheckForUpdatesThread.start() + + +class __SettingsWidget(QtWidgets.QDialog, settings_ui.Ui_DialogSettings): + def __init__(self, autosplit: AutoSplit): + super().__init__() + self.setupUi(self) + self.autosplit = autosplit + + def set_value(key: str, value: Any): + autosplit.settings_dict[key] = value + +# region Set initial values + # Hotkeys + self.split_input.setText(autosplit.settings_dict["split_hotkey"]) + self.reset_input.setText(autosplit.settings_dict["reset_hotkey"]) + self.undo_split_input.setText(autosplit.settings_dict["undo_split_hotkey"]) + self.skip_split_input.setText(autosplit.settings_dict["skip_split_hotkey"]) + self.pause_input.setText(autosplit.settings_dict["pause_hotkey"]) + + # Capture Settings + self.fps_limit_spinbox.setValue(autosplit.settings_dict["fps_limit"]) + self.live_capture_region_checkbox.setChecked(autosplit.settings_dict["live_capture_region"]) + self.force_print_window_checkbox.setChecked(autosplit.settings_dict["force_print_window"]) + + # Image Settings + self.default_comparison_method.setCurrentIndex(autosplit.settings_dict["default_comparison_method"]) + self.default_similarity_threshold_spinbox.setValue(autosplit.settings_dict["default_similarity_threshold"]) + self.default_delay_time_spinbox.setValue(autosplit.settings_dict["default_delay_time"]) + self.default_pause_time_spinbox.setValue(autosplit.settings_dict["default_pause_time"]) + self.loop_splits_checkbox.setChecked(autosplit.settings_dict["loop_splits"]) +# endregion +# region Binding + # Hotkeys + self.set_split_hotkey_button.clicked.connect(lambda: set_split_hotkey(self.autosplit)) + self.set_reset_hotkey_button.clicked.connect(lambda: set_reset_hotkey(self.autosplit)) + self.set_skip_split_hotkey_button.clicked.connect(lambda: set_skip_split_hotkey(self.autosplit)) + self.set_undo_split_hotkey_button.clicked.connect(lambda: set_undo_split_hotkey(self.autosplit)) + self.set_pause_hotkey_button.clicked.connect(lambda: set_pause_hotkey(self.autosplit)) + + # Capture Settings + self.fps_limit_spinbox.valueChanged.connect(lambda: set_value( + "fps_limit", + self.fps_limit_spinbox.value())) + self.live_capture_region_checkbox.stateChanged.connect(lambda: set_value( + "live_capture_region", + self.live_capture_region_checkbox.isChecked())) + self.force_print_window_checkbox.stateChanged.connect(lambda: set_value( + "force_print_window", + self.force_print_window_checkbox.isChecked())) + + # Image Settings + self.default_comparison_method.currentIndexChanged.connect(lambda: set_value( + "default_comparison_method", + self.default_comparison_method.currentIndex())) + self.default_similarity_threshold_spinbox.valueChanged.connect(lambda: set_value( + "default_similarity_threshold", + self.default_similarity_threshold_spinbox.value())) + self.default_delay_time_spinbox.valueChanged.connect(lambda: set_value( + "default_delay_time", + self.default_delay_time_spinbox.value())) + self.default_pause_time_spinbox.valueChanged.connect(lambda: set_value( + "default_pause_time", + self.default_pause_time_spinbox.value())) + self.loop_splits_checkbox.stateChanged.connect(lambda: set_value( + "loop_splits", + self.loop_splits_checkbox.isChecked())) +# endregion + + self.show() + + +def open_settings(autosplit: AutoSplit): + autosplit.SettingsWidget = __SettingsWidget(autosplit) + + +def get_default_settings_from_ui(autosplit: AutoSplit): + temp_dialog = QtWidgets.QDialog() + default_settings_dialog = settings_ui.Ui_DialogSettings() + default_settings_dialog.setupUi(temp_dialog) + default_settings: settings.SettingsDict = { + "split_hotkey": default_settings_dialog.split_input.text(), + "reset_hotkey": default_settings_dialog.reset_input.text(), + "undo_split_hotkey": default_settings_dialog.undo_split_input.text(), + "skip_split_hotkey": default_settings_dialog.skip_split_input.text(), + "pause_hotkey": default_settings_dialog.pause_input.text(), + "fps_limit": default_settings_dialog.fps_limit_spinbox.value(), + "live_capture_region": default_settings_dialog.live_capture_region_checkbox.isChecked(), + "force_print_window": default_settings_dialog.force_print_window_checkbox.isChecked(), + "default_comparison_method": default_settings_dialog.default_comparison_method.currentIndex(), + "default_similarity_threshold": default_settings_dialog.default_similarity_threshold_spinbox.value(), + "default_delay_time": default_settings_dialog.default_delay_time_spinbox.value(), + "default_pause_time": default_settings_dialog.default_pause_time_spinbox.value(), + "loop_splits": default_settings_dialog.loop_splits_checkbox.isChecked(), + + "split_image_directory": autosplit.split_image_folder_input.text(), + "captured_window_title": "", + "capture_region": Region( + autosplit.x_spinbox.value(), + autosplit.y_spinbox.value(), + autosplit.width_spinbox.value(), + autosplit.height_spinbox.value()) + } + del temp_dialog + return default_settings diff --git a/src/screen_region.py b/src/screen_region.py index 03bbf85e..2342dd8d 100644 --- a/src/screen_region.py +++ b/src/screen_region.py @@ -45,7 +45,7 @@ def select_region(autosplit: AutoSplit): error_messages.region() return autosplit.hwnd = hwnd - autosplit.window_text = window_text + autosplit.settings_dict["captured_window_title"] = window_text offset_x, offset_y, *_ = win32gui.GetWindowRect(autosplit.hwnd) __set_region_values(autosplit, @@ -76,7 +76,7 @@ def select_window(autosplit: AutoSplit): error_messages.region() return autosplit.hwnd = hwnd - autosplit.window_text = window_text + autosplit.settings_dict["captured_window_title"] = window_text # Getting window bounds # On Windows there is a shadow around the windows that we need to account for @@ -134,8 +134,8 @@ def align_region(autosplit: AutoSplit): # subregion being searched for to align the image. capture = capture_windows.capture_region( autosplit.hwnd, - autosplit.selection, - autosplit.force_print_window_checkbox.isChecked()) + autosplit.settings_dict["capture_region"], + autosplit.settings_dict["force_print_window"]) if capture is None: error_messages.region() @@ -151,25 +151,23 @@ def align_region(autosplit: AutoSplit): # The new region can be defined by using the min_loc point and the best_height and best_width of the template. __set_region_values(autosplit, - left=autosplit.selection.left + best_loc[0], - top=autosplit.selection.top + best_loc[1], + left=autosplit.settings_dict["capture_region"].x + best_loc[0], + top=autosplit.settings_dict["capture_region"].y + best_loc[1], width=best_width, height=best_height) def __set_region_values(autosplit: AutoSplit, left: int, top: int, width: int, height: int): - autosplit.selection.left = left - autosplit.selection.top = top - autosplit.selection.right = left + width - autosplit.selection.bottom = top + height + autosplit.settings_dict["capture_region"].x = left + autosplit.settings_dict["capture_region"].y = top + autosplit.settings_dict["capture_region"].width = width + autosplit.settings_dict["capture_region"].height = height autosplit.x_spinbox.setValue(left) autosplit.y_spinbox.setValue(top) autosplit.width_spinbox.setValue(width) autosplit.height_spinbox.setValue(height) - autosplit.check_live_image() - def __test_alignment(capture: cv2.ndarray, template: cv2.ndarray): # Obtain the best matching point for the template within the @@ -213,11 +211,11 @@ def __test_alignment(capture: cv2.ndarray, template: cv2.ndarray): def validate_before_parsing(autosplit: AutoSplit, show_error: bool = True, check_empty_directory: bool = True): error = None - if not autosplit.split_image_directory: + if not autosplit.settings_dict["split_image_directory"]: error = error_messages.split_image_directory - elif not os.path.isdir(autosplit.split_image_directory): + elif not os.path.isdir(autosplit.settings_dict["split_image_directory"]): error = error_messages.split_image_directory_not_found - elif check_empty_directory and not os.listdir(autosplit.split_image_directory): + elif check_empty_directory and not os.listdir(autosplit.settings_dict["split_image_directory"]): error = error_messages.split_image_directory_empty elif autosplit.hwnd <= 0 or not win32gui.GetWindowText(autosplit.hwnd): error = error_messages.region diff --git a/src/settings_file.py b/src/settings_file.py index c0315c23..cc6fec44 100644 --- a/src/settings_file.py +++ b/src/settings_file.py @@ -1,5 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, TypedDict + if TYPE_CHECKING: from AutoSplit import AutoSplit @@ -12,6 +13,7 @@ from PyQt6 import QtCore, QtWidgets import error_messages +from capture_windows import Region from gen import design from hotkeys import set_pause_hotkey, set_reset_hotkey, set_skip_split_hotkey, set_split_hotkey, set_undo_split_hotkey @@ -21,6 +23,26 @@ auto_split_directory = os.path.dirname(sys.executable if FROZEN else os.path.abspath(__file__)) +class SettingsDict(TypedDict): + split_hotkey: str + reset_hotkey: str + undo_split_hotkey: str + skip_split_hotkey: str + pause_hotkey: str + fps_limit: int + live_capture_region: bool + force_print_window: bool + default_comparison_method: int + default_similarity_threshold: float + default_delay_time: int + default_pause_time: float + loop_splits: bool + + split_image_directory: str + captured_window_title: str + capture_region: Region + + class RestrictedUnpickler(pickle.Unpickler): def find_class(self, module: str, name: str): @@ -29,27 +51,27 @@ def find_class(self, module: str, name: str): def get_save_settings_values(autosplit: AutoSplit): return [ - autosplit.split_image_directory, - autosplit.similarity_threshold_spinbox.value(), - autosplit.comparison_method_combobox.currentIndex(), - autosplit.pause_spinbox.value(), - int(autosplit.fps_limit_spinbox.value()), - autosplit.split_input.text(), - autosplit.reset_input.text(), - autosplit.skip_split_input.text(), - autosplit.undo_split_input.text(), - autosplit.pause_input.text(), - autosplit.x_spinbox.value(), - autosplit.y_spinbox.value(), - autosplit.width_spinbox.value(), - autosplit.height_spinbox.value(), - autosplit.window_text, + autosplit.settings_dict["split_image_directory"], + autosplit.settings_dict["default_similarity_threshold"], + autosplit.settings_dict["default_comparison_method"], + autosplit.settings_dict["default_pause_time"], + autosplit.settings_dict["fps_limit"], + autosplit.settings_dict["split_hotkey"], + autosplit.settings_dict["reset_hotkey"], + autosplit.settings_dict["skip_split_hotkey"], + autosplit.settings_dict["undo_split_hotkey"], + autosplit.settings_dict["pause_hotkey"], + autosplit.settings_dict["capture_region"].x, + autosplit.settings_dict["capture_region"].y, + autosplit.settings_dict["capture_region"].width, + autosplit.settings_dict["capture_region"].height, + autosplit.settings_dict["captured_window_title"], 0, 0, 1, - int(autosplit.loop_checkbox.isChecked()), - int(autosplit.auto_start_on_reset_checkbox.isChecked()), - autosplit.force_print_window_checkbox.isChecked()] + autosplit.settings_dict["loop_splits"], + 0, + autosplit.settings_dict["force_print_window"]] def have_settings_changed(autosplit: AutoSplit): @@ -123,12 +145,12 @@ def __load_settings_from_file(autosplit: AutoSplit, load_settings_file_path: str autosplit.show_error_signal.emit(error_messages.invalid_settings) return False - autosplit.split_image_directory = settings[0] + autosplit.settings_dict["split_image_directory"] = settings[0] autosplit.split_image_folder_input.setText(settings[0]) - autosplit.similarity_threshold_spinbox.setValue(settings[1]) - autosplit.comparison_method_combobox.setCurrentIndex(settings[2]) - autosplit.pause_spinbox.setValue(settings[3]) - autosplit.fps_limit_spinbox.setValue(settings[4]) + autosplit.settings_dict["default_similarity_threshold"] = settings[1] + autosplit.settings_dict["default_comparison_method"] = settings[2] + autosplit.settings_dict["default_pause_time"] = settings[3] + autosplit.settings_dict["fps_limit"] = settings[4] keyboard.unhook_all() if not autosplit.is_auto_controlled: set_split_hotkey(autosplit, settings[5]) @@ -140,20 +162,19 @@ def __load_settings_from_file(autosplit: AutoSplit, load_settings_file_path: str autosplit.y_spinbox.setValue(settings[11]) autosplit.width_spinbox.setValue(settings[12]) autosplit.height_spinbox.setValue(settings[13]) - autosplit.window_text = settings[14] - autosplit.loop_checkbox.setChecked(bool(settings[18])) - autosplit.auto_start_on_reset_checkbox.setChecked(bool(settings[19])) - autosplit.force_print_window_checkbox.setChecked(settings[20]) + autosplit.settings_dict["captured_window_title"] = settings[14] + autosplit.settings_dict["loop_splits"] = settings[18] + autosplit.settings_dict["force_print_window"] = settings[20] - if autosplit.window_text: + if autosplit.settings_dict["captured_window_title"]: # https://github.com/kaluluosi/pywin32-stubs/issues/7 - hwnd = win32gui.FindWindow(None, autosplit.window_text) # type: ignore + hwnd = win32gui.FindWindow(None, autosplit.settings_dict["captured_window_title"]) # type: ignore if hwnd: autosplit.hwnd = hwnd else: autosplit.live_image.setText("Reload settings after opening" - f'\n"{autosplit.window_text}"' - "\nto automatically load Live Capture") + + f'\n"{autosplit.settings_dict["captured_window_title"]}"' + + "\nto automatically load Capture Region") return True @@ -170,7 +191,6 @@ def load_settings( return autosplit.last_successfully_loaded_settings_file_path = load_settings_file_path - autosplit.check_live_image() autosplit.load_start_image() diff --git a/src/split_parser.py b/src/split_parser.py index 27862ed9..6e4a584f 100644 --- a/src/split_parser.py +++ b/src/split_parser.py @@ -153,9 +153,9 @@ def __pop_image_type(split_image: list[AutoSplitImage], image_type: ImageType): def parse_and_validate_images(autosplit: AutoSplit): # Get split images all_images = [ - AutoSplitImage(os.path.join(autosplit.split_image_directory, image_name)) + AutoSplitImage(os.path.join(autosplit.settings_dict["split_image_directory"], image_name)) for image_name - in os.listdir(autosplit.split_image_directory)] + in os.listdir(autosplit.settings_dict["split_image_directory"])] # Find non-split images and then remove them from the list autosplit.start_image = __pop_image_type(all_images, ImageType.START) @@ -171,7 +171,7 @@ def parse_and_validate_images(autosplit: AutoSplit): return False # error out if there is a {p} flag but no pause hotkey set and is not auto controlled. - if (not autosplit.pause_input.text() + if (not autosplit.settings_dict["pause_hotkey"] and image.check_flag(PAUSE_FLAG) and not autosplit.is_auto_controlled): autosplit.gui_changes_on_reset() @@ -181,7 +181,7 @@ def parse_and_validate_images(autosplit: AutoSplit): # Check that there's only one reset image if image.image_type == ImageType.RESET: # If there is no reset hotkey set but a reset image is present, and is not auto controlled, throw an error. - if not autosplit.reset_input.text() and not autosplit.is_auto_controlled: + if not autosplit.settings_dict["reset_hotkey"] and not autosplit.is_auto_controlled: autosplit.gui_changes_on_reset() error_messages.reset_hotkey() return False From 58389317425a740a8493c2a2a0f1986447be477a Mon Sep 17 00:00:00 2001 From: Avasam Date: Thu, 16 Dec 2021 00:25:03 -0500 Subject: [PATCH 5/7] Better "seconds remaining" text --- res/settings.ui | 5 +-- src/AutoSplit.py | 20 +++++---- src/hotkeys.py | 18 ++++---- typings/keyboard/__init__.pyi | 62 ++++++++++++++++----------- typings/keyboard/_canonical_names.pyi | 11 +++++ 5 files changed, 69 insertions(+), 47 deletions(-) create mode 100644 typings/keyboard/_canonical_names.pyi diff --git a/res/settings.ui b/res/settings.ui index a388d7d6..ee4dfd38 100644 --- a/res/settings.ui +++ b/res/settings.ui @@ -427,7 +427,7 @@ - false + true @@ -477,9 +477,6 @@ - - true - true diff --git a/src/AutoSplit.py b/src/AutoSplit.py index 6fd87522..ac5c8830 100644 --- a/src/AutoSplit.py +++ b/src/AutoSplit.py @@ -293,9 +293,9 @@ def __start_image_function(self): or not self.start_image \ or time() < self.check_start_image_timestamp \ or (not self.settings_dict["split_hotkey"] and not self.is_auto_controlled): - pause_time_left = f"{self.check_start_image_timestamp - time():.1f}" + pause_time_left = self.check_start_image_timestamp - time() self.current_split_image.setText( - f"None\n (Paused before loading Start Image).\n {pause_time_left} sec remaining") + f"None\n (Paused before loading Start Image).\n {seconds_remaining_text(pause_time_left)}") return if self.check_start_image_timestamp > 0: @@ -340,9 +340,9 @@ def __start_image_function(self): delay_start_time = time() start_delay = self.start_image.delay / 1000 while time() - delay_start_time < start_delay: - delay_time_left = round(start_delay - (time() - delay_start_time), 1) + delay_time_left = start_delay - (time() - delay_start_time) self.current_split_image.setText( - f"Delayed Before Starting:\n {delay_time_left} sec remaining") + f"Delayed Before Starting:\n {seconds_remaining_text(delay_time_left)}") # Email sent to pyqt@riverbankcomputing.com QtTest.QTest.qWait(1) # type: ignore @@ -629,8 +629,8 @@ def __auto_splitter(self): # check for reset while delayed and display a counter of the remaining split delay time delay_start_time = time() while time() - delay_start_time < split_delay: - delay_time_left = round(split_delay - (time() - delay_start_time), 1) - self.current_split_image.setText(f"Delayed Split: {delay_time_left} sec remaining") + delay_time_left = split_delay - (time() - delay_start_time) + self.current_split_image.setText(f"Delayed Split: {seconds_remaining_text(delay_time_left)}") if self.__check_for_reset(): return @@ -677,8 +677,8 @@ def __auto_splitter(self): if pause_time > 0: pause_start_time = time() while time() - pause_start_time < pause_time: - pause_time_left = round(pause_time - (time() - pause_start_time), 1) - self.current_split_image.setText(f"None (Paused). {pause_time_left} sec remaining") + pause_time_left = pause_time - (time() - pause_start_time) + self.current_split_image.setText(f"None (Paused). {seconds_remaining_text(pause_time_left)}") if self.__check_for_reset(): return @@ -862,6 +862,10 @@ def exit_program(): exit_program() +def seconds_remaining_text(seconds: float): + return f"{seconds:.1f} second{'' if 0 < seconds <= 1 else 's'} remaining" + + def main(): # Call to QApplication outside the try-except so we can show error messages app = QApplication(sys.argv) diff --git a/src/hotkeys.py b/src/hotkeys.py index 3a6901e2..9fcdf23c 100644 --- a/src/hotkeys.py +++ b/src/hotkeys.py @@ -6,10 +6,10 @@ from AutoSplit import AutoSplit import threading -from keyboard._keyboard_event import KeyboardEvent, KEY_DOWN + import keyboard # https://github.com/boppreh/keyboard/issues/505 import pyautogui # https://github.com/asweigart/pyautogui/issues/645 -# While not usually recommended, we don'thread manipulate the mouse, and we don'thread want the extra delay +# While not usually recommended, we don't manipulate the mouse, and we don't want the extra delay pyautogui.FAILSAFE = False SET_HOTKEY_TEXT = "Set Hotkey" @@ -100,11 +100,11 @@ def _send_hotkey(key_or_scan_code: Union[int, str]): pyautogui.hotkey(key_or_scan_code.replace(" ", "")) -def __validate_keypad(expected_key: str, keyboard_event: KeyboardEvent) -> bool: +def __validate_keypad(expected_key: str, keyboard_event: keyboard.KeyboardEvent) -> bool: # Prevent "(keypad)delete", "(keypad)./decimal" and "del" from triggering each other # as well as "." and "(keypad)./decimal" if keyboard_event.scan_code in {83, 52}: - # TODO: "del" won'thread work with "(keypad)delete" if localized in non-english (ie: "suppr" in french) + # TODO: "del" won't work with "(keypad)delete" if localized in non-english (ie: "suppr" in french) return expected_key == keyboard_event.name # Prevent "action keys" from triggering "keypad keys" if keyboard_event.name and is_digit(keyboard_event.name[-1]): @@ -125,16 +125,16 @@ def __validate_keypad(expected_key: str, keyboard_event: KeyboardEvent) -> bool: # We're doing the check here instead of saving the key code because it'll # cause issues with save files and the non-keypad shared keys are localized -# while the keypad ones aren'thread. +# while the keypad ones aren't. -# Since we reuse the key string we set to send to LiveSplit, we can'thread use fake names like "num home". +# Since we reuse the key string we set to send to LiveSplit, we can't use fake names like "num home". # We're also trying to achieve the same hotkey behaviour as LiveSplit has. -def _hotkey_action(keyboard_event: KeyboardEvent, key_name: str, action: Callable[[], None]): - if keyboard_event.event_type == KEY_DOWN and __validate_keypad(key_name, keyboard_event): +def _hotkey_action(keyboard_event: keyboard.KeyboardEvent, key_name: str, action: Callable[[], None]): + if keyboard_event.event_type == keyboard.KEY_DOWN and __validate_keypad(key_name, keyboard_event): action() -def __get_key_name(keyboard_event: KeyboardEvent): +def __get_key_name(keyboard_event: keyboard.KeyboardEvent): return f"num {keyboard_event.name}" \ if keyboard_event.is_keypad and is_digit(keyboard_event.name) \ else str(keyboard_event.name) diff --git a/typings/keyboard/__init__.pyi b/typings/keyboard/__init__.pyi index 18f68af6..86bd06ec 100644 --- a/typings/keyboard/__init__.pyi +++ b/typings/keyboard/__init__.pyi @@ -3,6 +3,7 @@ This type stub file was generated by pyright. """ from __future__ import print_function as _print_function import typing +import collections.abc import re as _re import itertools as _itertools @@ -13,6 +14,7 @@ from threading import Lock as _Lock, Thread as _Thread from ._keyboard_event import KEY_DOWN, KEY_UP, KeyboardEvent from ._generic import GenericListener as _GenericListener from ._canonical_names import all_modifiers, normalize_name, sided_modifiers +__all__ = ["all_modifiers", "normalize_name", "sided_modifiers", "KEY_DOWN", "KEY_UP", "KeyboardEvent"] try: # Python2 @@ -103,12 +105,12 @@ key events. In this case `keyboard` will be unable to report events. - This program makes no attempt to hide itself, so don't use it for keyloggers or online gaming bots. Be responsible. """ -Callback = typing.Callable[[KeyboardEvent], None] +Callback = collections.abc.Callable[[KeyboardEvent], None] version: str -_is_str = typing.Callable[[typing.Any], bool] -_is_number = typing.Callable[[typing.Any], bool] -_is_list: typing.Callable[[typing.Any], bool] +_is_str = collections.abc.Callable[[typing.Any], bool] +_is_number = collections.abc.Callable[[typing.Any], bool] +_is_list: collections.abc.Callable[[typing.Any], bool] class _State: @@ -120,10 +122,6 @@ class _Event(_UninterruptibleEvent): ... -if _platform.system() == 'Windows': - ... -else: - ... _modifier_scan_codes: set @@ -197,14 +195,16 @@ class _KeyboardListener(_GenericListener): _listener: _KeyboardListener -def key_to_scan_codes(key: typing.Union[int, str, typing.List[typing.Union[int, str]]], error_if_missing: bool = ...) -> typing.List[int]: +def key_to_scan_codes(key: typing.Union[int, str, typing.List[typing.Union[int, str]]], + error_if_missing: bool = ...) -> typing.List[int]: """ Returns a list of scan codes associated with this key (name or scan code). """ ... -def parse_hotkey(hotkey) -> tuple[tuple[tuple[Unknown] | Unknown | tuple[()] | tuple[Unknown, ...]]] | tuple[tuple[tuple[Unknown] | Unknown | tuple[()] | tuple[Unknown, ...], ...]] | tuple[Unknown, ...]: +def parse_hotkey(hotkey) -> tuple[tuple[tuple[Unknown] | Unknown | tuple[()] | tuple[Unknown, ...]] + ] | tuple[tuple[tuple[Unknown] | Unknown | tuple[()] | tuple[Unknown, ...], ...]] | tuple[Unknown, ...]: """ Parses a user-provided hotkey into nested tuples representing the parsed structure, with the bottom values being lists of scan codes. @@ -274,10 +274,10 @@ def call_later(fn, args=..., delay=...) -> None: ... -_hooks: dict[typing.Callable, Unknown] +_hooks: dict[collections.abc.Callable, Unknown] -def hook(callback: Callback, suppress=..., on_remove=...) -> typing.Callable[[], None]: +def hook(callback: Callback, suppress=..., on_remove=...) -> collections.abc.Callable[[], None]: """ Installs a global listener on all available keyboards, invoking `callback` each time a key is pressed or released. @@ -296,21 +296,22 @@ def hook(callback: Callback, suppress=..., on_remove=...) -> typing.Callable[[], ... -def on_press(callback: Callback, suppress=...) -> typing.Callable[[], None]: +def on_press(callback: Callback, suppress=...) -> collections.abc.Callable[[], None]: """ Invokes `callback` for every KEY_DOWN event. For details see `hook`. """ ... -def on_release(callback: Callback, suppress=...) -> typing.Callable[[], None]: +def on_release(callback: Callback, suppress=...) -> collections.abc.Callable[[], None]: """ Invokes `callback` for every KEY_UP event. For details see `hook`. """ ... -def hook_key(key: typing.Union[int, str, typing.List[typing.Union[int, str]]], callback: Callback, suppress: bool = ...) -> typing.Callable[[], None]: +def hook_key(key: typing.Union[int, str, typing.List[typing.Union[int, str]]], + callback: Callback, suppress: bool = ...) -> collections.abc.Callable[[], None]: """ Hooks key up and key down events for a single key. Returns the event handler created. To remove a hooked key use `unhook_key(key)` or @@ -322,21 +323,21 @@ def hook_key(key: typing.Union[int, str, typing.List[typing.Union[int, str]]], c ... -def on_press_key(key, callback: Callback, suppress=...) -> typing.Callable[[], None]: +def on_press_key(key, callback: Callback, suppress=...) -> collections.abc.Callable[[], None]: """ Invokes `callback` for KEY_DOWN event related to the given key. For details see `hook`. """ ... -def on_release_key(key, callback: Callback, suppress=...) -> typing.Callable[[], None]: +def on_release_key(key, callback: Callback, suppress=...) -> collections.abc.Callable[[], None]: """ Invokes `callback` for KEY_UP event related to the given key. For details see `hook`. """ ... -def unhook(remove: typing.Callable[[], None]) -> None: +def unhook(remove: collections.abc.Callable[[], None]) -> None: """ Removes a previously added hook, either by callback or by the return value of `hook`. @@ -355,7 +356,7 @@ def unhook_all() -> None: ... -def block_key(key) -> typing.Callable[[], None]: +def block_key(key) -> collections.abc.Callable[[], None]: """ Suppresses all key events of the given key, regardless of modifiers. """ @@ -365,7 +366,7 @@ def block_key(key) -> typing.Callable[[], None]: unblock_key = unhook_key -def remap_key(src, dst) -> typing.Callable[[], None]: +def remap_key(src, dst) -> collections.abc.Callable[[], None]: """ Whenever the key `src` is pressed or released, regardless of modifiers, press or release the hotkey `dst` instead. @@ -388,7 +389,8 @@ def parse_hotkey_combinations(hotkey) -> tuple[tuple[tuple[Unknown, ...], ...], _hotkeys: dict -def add_hotkey(hotkey, callback: Callback, args=..., suppress=..., timeout=..., trigger_on_release=...) -> typing.Callable[[], None]: +def add_hotkey(hotkey, callback: collections.abc.Callable, args=..., suppress=..., timeout=..., + trigger_on_release=...) -> collections.abc.Callable[[], None]: """ Invokes a callback every time a hotkey is pressed. The hotkey must be in the format `ctrl+shift+a, s`. This would trigger when the user holds @@ -453,7 +455,7 @@ def unhook_all_hotkeys() -> None: unregister_all_hotkeys = remove_all_hotkeys = clear_all_hotkeys = unhook_all_hotkeys -def remap_hotkey(src, dst, suppress=..., trigger_on_release=...) -> typing.Callable[[], None]: +def remap_hotkey(src, dst, suppress=..., trigger_on_release=...) -> collections.abc.Callable[[], None]: """ Whenever the hotkey `src` is pressed, suppress it and send `dst` instead. @@ -589,10 +591,11 @@ def get_typed_strings(events, allow_backspace=...): ... -_recording: typing.Optional[tuple[Unknown | _queue.Queue[Unknown], typing.Callable[[], None]]] +_recording: typing.Optional[tuple[Unknown | _queue.Queue[Unknown], collections.abc.Callable[[], None]]] -def start_recording(recorded_events_queue=...) -> tuple[Unknown | _queue.Queue[Unknown], typing.Callable[[], None]]: +def start_recording(recorded_events_queue=...) -> tuple[Unknown + | _queue.Queue[Unknown], collections.abc.Callable[[], None]]: """ Starts recording all keyboard events into a global variable, or the given queue if any. Returns the queue of events and the hooked function. @@ -639,7 +642,13 @@ replay = play _word_listeners: dict -def add_word_listener(word, callback: Callback, triggers=..., match_suffix=..., timeout=...) -> typing.Callable[[], None]: +def add_word_listener( + word, + callback: Callback, + triggers=..., + match_suffix=..., + timeout=...) -> collections.abc.Callable[[], + None]: """ Invokes a callback every time a sequence of characters is typed (e.g. 'pet') and followed by a trigger key (e.g. space). Modifiers (e.g. alt, ctrl, @@ -676,7 +685,8 @@ def remove_word_listener(word_or_handler) -> None: ... -def add_abbreviation(source_text, replacement_text, match_suffix=..., timeout=...) -> typing.Callable[[], None]: +def add_abbreviation(source_text, replacement_text, match_suffix=..., + timeout=...) -> collections.abc.Callable[[], None]: """ Registers a hotkey that replaces one typed text with another. For example diff --git a/typings/keyboard/_canonical_names.pyi b/typings/keyboard/_canonical_names.pyi new file mode 100644 index 00000000..94cb7a64 --- /dev/null +++ b/typings/keyboard/_canonical_names.pyi @@ -0,0 +1,11 @@ +""" +This type stub file was generated by pyright. +""" + +canonical_names = ... +sided_modifiers = ... +all_modifiers = ... + + +def normalize_name(name: str) -> str: + ... From 21947d09708dad9d9f8ccf796e73f84d49d6959b Mon Sep 17 00:00:00 2001 From: Avasam Date: Wed, 22 Dec 2021 12:33:36 -0500 Subject: [PATCH 6/7] Undo split goes to the end of the group --- README.md | 4 ++-- src/AutoControlledWorker.py | 2 ++ src/AutoSplit.py | 8 +++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 4084e5e5..4c9e24c4 100644 --- a/README.md +++ b/README.md @@ -155,9 +155,9 @@ Given these splits: 1 dummy, 2 normal, 3 dummy, 4 dummy, 5 normal, 6 normal. In this situation you would have only 3 splits in LiveSplit/wsplit (even though there are 6 split images, only 3 are "real" splits). This basically results in 3 groups of splits: 1st split is images 1 and 2. 2nd split is images 3, 4 and 5. 3rd split is image 6. - If you are in the 1st or 2nd image and press the skip key, it will end up on the 3rd image -- If you are in the 3rd, 4th or 5th image and press the undo key, it will end up on the 1st image +- If you are in the 3rd, 4th or 5th image and press the undo key, it will end up on the 2nd image - If you are in the 3rd, 4th or 5th image and press the skip key, it will end up on the 6th image -- If you are in the 6th image and press the undo key, it will end up on the 3rd image +- If you are in the 6th image and press the undo key, it will end up on the 5th image ### Loop Split Images diff --git a/src/AutoControlledWorker.py b/src/AutoControlledWorker.py index 0c252d44..36b231d9 100644 --- a/src/AutoControlledWorker.py +++ b/src/AutoControlledWorker.py @@ -20,6 +20,8 @@ def run(self): except RuntimeError: self.autosplit.show_error_signal.emit(error_messages.stdin_lost) break + except EOFError: + continue # TODO: "AutoSplit Integration" needs to call this and wait instead of outright killing the app. # For now this can only used in a Development environment if line == "kill": diff --git a/src/AutoSplit.py b/src/AutoSplit.py index ac5c8830..91d8b88f 100644 --- a/src/AutoSplit.py +++ b/src/AutoSplit.py @@ -54,7 +54,7 @@ def excepthook(exception_type: type[BaseException], exception: BaseException, _t sys.exit(0) autosplit.show_error_signal.emit(lambda: error_messages.exception_traceback( "AutoSplit encountered an unhandled exception and will try to recover, " - + f"however, there is no guarantee everything will work properly. {CREATE_NEW_ISSUE_MESSAGE}", + + f"however, there is no guarantee it will keep working properly. {CREATE_NEW_ISSUE_MESSAGE}", exception)) return excepthook @@ -411,9 +411,7 @@ def __check_fps(self): while count < CHECK_FPS_ITERATIONS: capture = self.__get_capture_for_comparison() _ = image.compare_with_capture(self, capture) - set_ui_image(self.current_split_image, image.bytes, True) count += 1 - self.current_split_image.clear() # calculate FPS t1 = time() @@ -437,9 +435,9 @@ def __undo_split(self, navigate_image_only: bool = False): return if not navigate_image_only: - for i, group in enumerate(self.split_groups): + for i, group in enumerate(self.split_groups,): if i > 0 and self.split_image_number in group: - self.split_image_number = self.split_groups[i - 1][0] + self.split_image_number = self.split_groups[i - 1][-1] break else: self.split_image_number -= 1 From 83039c60997fc80db703e507522b5d7819534139 Mon Sep 17 00:00:00 2001 From: Avasam Date: Wed, 29 Dec 2021 18:57:22 -0500 Subject: [PATCH 7/7] Fixed images comparison method defaulting to 0 rather than none --- src/split_parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/split_parser.py b/src/split_parser.py index 73247626..ee96f1e9 100644 --- a/src/split_parser.py +++ b/src/split_parser.py @@ -110,7 +110,7 @@ def comparison_method_from_filename(filename: str): # Check to make sure there is a valid delay time between brackets # of the filename - value = __value_from_filename(filename, "<>", 0) + value = __value_from_filename(filename, "<>", -1) # Comparison method should always be positive or zero return value if value >= 0 else None