def test_starting_location_world_select(skip_qtbot, default_preset): # Setup preset = dataclasses.replace( default_preset, uuid=uuid.UUID('b41fde84-1f57-4b79-8cd6-3e5a78077fa6'), base_preset_uuid=default_preset.uuid) editor = PresetEditor(preset) window = LogicSettingsWindow(None, editor) skip_qtbot.addWidget(window) # Run checkbox_list = window._starting_location_for_world window.on_preset_changed(editor.create_custom_preset_with()) assert len(checkbox_list) == 10 temple_grounds_checkbox = checkbox_list["Temple Grounds"] assert temple_grounds_checkbox.checkState() == Qt.PartiallyChecked skip_qtbot.mouseClick(temple_grounds_checkbox, Qt.LeftButton) assert temple_grounds_checkbox.checkState() == Qt.Checked assert len(editor.configuration.starting_location.locations) == 39 skip_qtbot.mouseClick(temple_grounds_checkbox, Qt.LeftButton) assert temple_grounds_checkbox.checkState() == Qt.Unchecked assert len(editor.configuration.starting_location.locations) == 0 skip_qtbot.mouseClick(temple_grounds_checkbox, Qt.LeftButton) window.on_preset_changed(editor.create_custom_preset_with()) assert temple_grounds_checkbox.checkState() == Qt.Checked assert len(editor.configuration.starting_location.locations) == 39
def test_on_preset_changed(skip_qtbot, preset_manager, game): # Setup editor = PresetEditor( preset_manager.default_preset_for_game(game).get_preset()) window = LogicSettingsWindow(None, editor) # Run window.on_preset_changed(editor.create_custom_preset_with())
def test_on_preset_changed(skip_qtbot, preset_manager, game): # Setup base = preset_manager.default_preset_for_game(game).get_preset() preset = dataclasses.replace( base, uuid=uuid.UUID('b41fde84-1f57-4b79-8cd6-3e5a78077fa6'), base_preset_uuid=base.uuid) editor = PresetEditor(preset) window = LogicSettingsWindow(None, editor) # Run window.on_preset_changed(editor.create_custom_preset_with())
def test_starting_location_world_select(skip_qtbot, default_preset): # Setup editor = PresetEditor(default_preset) window = LogicSettingsWindow(None, editor) skip_qtbot.addWidget(window) # Run checkbox_list = window._starting_location_for_world window.on_preset_changed(editor.create_custom_preset_with()) assert len(checkbox_list) == 10 temple_grounds_checkbox = checkbox_list["Temple Grounds"] assert temple_grounds_checkbox.checkState() == Qt.PartiallyChecked skip_qtbot.mouseClick(temple_grounds_checkbox, Qt.LeftButton) assert temple_grounds_checkbox.checkState() == Qt.Checked assert len(editor.configuration.starting_location.locations) == 39 skip_qtbot.mouseClick(temple_grounds_checkbox, Qt.LeftButton) assert temple_grounds_checkbox.checkState() == Qt.Unchecked assert len(editor.configuration.starting_location.locations) == 0 skip_qtbot.mouseClick(temple_grounds_checkbox, Qt.LeftButton) window.on_preset_changed(editor.create_custom_preset_with()) assert temple_grounds_checkbox.checkState() == Qt.Checked assert len(editor.configuration.starting_location.locations) == 39
class GenerateSeedTab(QWidget, BackgroundTaskMixin): _logic_settings_window: Optional[LogicSettingsWindow] = None _has_preset: bool = False _tool_button_menu: QMenu _action_delete: QAction def __init__(self, window: Ui_MainWindow, window_manager: WindowManager, options: Options): super().__init__() self.window = window self._window_manager = window_manager self.failure_handler = GenerationFailureHandler(self) self._options = options def setup_ui(self): window = self.window # Progress self.background_tasks_button_lock_signal.connect( self.enable_buttons_with_background_tasks) self.progress_update_signal.connect(self.update_progress) self.window.stop_background_process_button.clicked.connect( self.stop_background_process) for game in RandovaniaGame: self.window.create_choose_game_combo.addItem(game.long_name, game) self.window.create_choose_game_combo.setVisible( self._window_manager.is_preview_mode) self.window.create_choose_game_label.setVisible( self._window_manager.is_preview_mode) self.window.num_players_spin_box.setVisible( self._window_manager.is_preview_mode) # Menu self._tool_button_menu = QMenu(window.preset_tool_button) window.preset_tool_button.setMenu(self._tool_button_menu) self._action_delete = QAction(window) self._action_delete.setText("Delete") self._tool_button_menu.addAction(self._action_delete) action_export_preset = QAction(window) action_export_preset.setText("Export") self._tool_button_menu.addAction(action_export_preset) action_import_preset = QAction(window) action_import_preset.setText("Import") self._tool_button_menu.addAction(action_import_preset) # Signals window.create_choose_game_combo.activated.connect(self._on_select_game) window.preset_tool_button.clicked.connect(self._on_customize_button) window.create_preset_combo.activated.connect(self._on_select_preset) window.create_generate_button.clicked.connect( partial(self._generate_new_seed, True)) window.create_generate_race_button.clicked.connect( partial(self._generate_new_seed, False)) self._action_delete.triggered.connect(self._on_delete_preset) action_export_preset.triggered.connect(self._on_export_preset) action_import_preset.triggered.connect(self._on_import_preset) @property def _current_preset_data(self) -> Optional[VersionedPreset]: return self._window_manager.preset_manager.preset_for_name( self.window.create_preset_combo.currentData()) def enable_buttons_with_background_tasks(self, value: bool): self.window.stop_background_process_button.setEnabled(not value) self.window.create_generate_button.setEnabled(value) self.window.create_generate_race_button.setEnabled(value) def _create_button_for_preset(self, preset: VersionedPreset): create_preset_combo = self.window.create_preset_combo create_preset_combo.addItem(preset.name, preset.name) def _add_new_preset(self, preset: VersionedPreset): with self._options as options: options.selected_preset_name = preset.name if self._window_manager.preset_manager.add_new_preset(preset): self._create_button_for_preset(preset) self.on_preset_changed(preset.get_preset()) @asyncSlot() async def _on_customize_button(self): if self._logic_settings_window is not None: self._logic_settings_window.raise_() return editor = PresetEditor(self._current_preset_data.get_preset()) self._logic_settings_window = LogicSettingsWindow( self._window_manager, editor) self._logic_settings_window.on_preset_changed( editor.create_custom_preset_with()) editor.on_changed = lambda: self._logic_settings_window.on_preset_changed( editor.create_custom_preset_with()) result = await async_dialog.execute_dialog(self._logic_settings_window) self._logic_settings_window = None if result == QDialog.Accepted: self._add_new_preset( VersionedPreset.with_preset( editor.create_custom_preset_with())) def _on_delete_preset(self): self._window_manager.preset_manager.delete_preset( self._current_preset_data) self.window.create_preset_combo.removeItem( self.window.create_preset_combo.currentIndex()) self._on_select_preset() def _on_export_preset(self): path = common_qt_lib.prompt_user_for_preset_file(self._window_manager, new_file=True) if path is not None: self._current_preset_data.save_to_file(path) def _on_import_preset(self): path = common_qt_lib.prompt_user_for_preset_file(self._window_manager, new_file=False) if path is None: return preset = VersionedPreset.from_file_sync(path) try: preset.get_preset() except (ValueError, KeyError): QMessageBox.critical( self._window_manager, "Error loading preset", "The file at '{}' contains an invalid preset.".format(path)) return if self._window_manager.preset_manager.preset_for_name( preset.name) is not None: user_response = QMessageBox.warning( self._window_manager, "Preset name conflict", "A preset named '{}' already exists. Do you want to overwrite it?" .format(preset.name), QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if user_response == QMessageBox.No: return self._add_new_preset(preset) def select_game(self, game: RandovaniaGame): combo_index = self.window.create_choose_game_combo.findData(game) self.window.create_choose_game_combo.setCurrentIndex(combo_index) self._update_create_preset_combo(game) def _on_select_game(self): game = self.window.create_choose_game_combo.currentData() self._update_create_preset_combo(game) self._on_select_preset() def _update_create_preset_combo(self, game: RandovaniaGame): self.window.create_preset_combo.clear() for preset in self._window_manager.preset_manager.all_presets: if preset.game == game: self._create_button_for_preset(preset) def _on_select_preset(self): preset_data = self._current_preset_data try: self.on_preset_changed(preset_data.get_preset()) except InvalidPreset as e: logging.exception(f"Invalid preset for {preset_data.name}") QMessageBox.warning( self._window_manager, "Incompatible Preset", f"Preset {preset_data.name} can't be used as it contains the following error:\n{e.original_exception}" ) self.window.create_preset_combo.setCurrentIndex(0) self.on_preset_changed(self._window_manager.preset_manager. default_preset.get_preset()) return with self._options as options: options.selected_preset_name = preset_data.name # Generate seed def _generate_new_seed(self, spoiler: bool): preset = self._current_preset_data num_players = self.window.num_players_spin_box.value() self.generate_seed_from_permalink( Permalink( seed_number=random.randint(0, 2**31), spoiler=spoiler, presets={i: preset.get_preset() for i in range(num_players)}, )) def generate_seed_from_permalink(self, permalink: Permalink): def work(progress_update: ProgressUpdateCallable): try: layout = simplified_patcher.generate_layout( progress_update=progress_update, permalink=permalink, options=self._options) progress_update( f"Success! (Seed hash: {layout.shareable_hash})", 1) persist_layout(self._options.data_dir, layout) self._window_manager.open_game_details(layout) except GenerationFailure as generate_exception: self.failure_handler.handle_failure(generate_exception) progress_update( "Generation Failure: {}".format(generate_exception), -1) if self._window_manager.is_preview_mode: print(f"Permalink: {permalink.as_base64_str}") self.run_in_background_thread(work, "Creating a seed...") def on_options_changed(self, options: Options): if not self._has_preset: selected_preset = self._window_manager.preset_manager.preset_for_name( options.selected_preset_name) if selected_preset is not None: self.select_game(selected_preset.game) index = self.window.create_preset_combo.findText( selected_preset.name) if index != -1: self.window.create_preset_combo.setCurrentIndex(index) try: self.on_preset_changed( self._current_preset_data.get_preset()) return except InvalidPreset: logging.exception( f"Invalid preset for {options.selected_preset_name}" ) else: self.select_game(RandovaniaGame.PRIME2) self.window.create_preset_combo.setCurrentIndex(0) self.on_preset_changed(self._window_manager.preset_manager. default_preset.get_preset()) def on_preset_changed(self, preset: Preset): self._has_preset = True self.window.create_preset_description.setText(preset.description) self._action_delete.setEnabled(preset.base_preset_name is not None) create_preset_combo = self.window.create_preset_combo create_preset_combo.setCurrentIndex( create_preset_combo.findText(preset.name)) categories = list(preset_describer.describe(preset)) left_categories = categories[::2] right_categories = categories[1::2] self.window.create_describe_left_label.setText( preset_describer.merge_categories(left_categories)) self.window.create_describe_right_label.setText( preset_describer.merge_categories(right_categories)) def update_progress(self, message: str, percentage: int): self.window.progress_label.setText(message) if "Aborted" in message: percentage = 0 if percentage >= 0: self.window.progress_bar.setRange(0, 100) self.window.progress_bar.setValue(percentage) else: self.window.progress_bar.setRange(0, 0)
class GenerateSeedTab(QtWidgets.QWidget, BackgroundTaskMixin): _logic_settings_window: Optional[LogicSettingsWindow] = None _has_set_from_last_selected: bool = False _preset_menu: PresetMenu _action_delete: QtWidgets.QAction _original_show_event: Callable[[QtGui.QShowEvent], None] def __init__(self, window: Ui_MainWindow, window_manager: WindowManager, options: Options): super().__init__() self.window = window self._window_manager = window_manager self.failure_handler = GenerationFailureHandler(self) self._options = options def setup_ui(self): window = self.window window.create_preset_tree.window_manager = self._window_manager self._original_show_event = window.tab_create_seed.showEvent window.tab_create_seed.showEvent = self._tab_show_event # Progress self.background_tasks_button_lock_signal.connect( self.enable_buttons_with_background_tasks) self.progress_update_signal.connect(self.update_progress) self.window.stop_background_process_button.clicked.connect( self.stop_background_process) self.window.num_players_spin_box.setVisible( self._window_manager.is_preview_mode) self.window.create_generate_no_retry_button.setVisible( self._window_manager.is_preview_mode) # Menu self._preset_menu = PresetMenu(window) # Signals window.create_generate_button.clicked.connect( partial(self._generate_new_seed, True)) window.create_generate_no_retry_button.clicked.connect( partial(self._generate_new_seed, True, retries=0)) window.create_generate_race_button.clicked.connect( partial(self._generate_new_seed, False)) window.create_preset_tree.itemSelectionChanged.connect( self._on_select_preset) window.create_preset_tree.customContextMenuRequested.connect( self._on_tree_context_menu) self._preset_menu.action_customize.triggered.connect( self._on_customize_preset) self._preset_menu.action_delete.triggered.connect( self._on_delete_preset) self._preset_menu.action_history.triggered.connect( self._on_view_preset_history) self._preset_menu.action_export.triggered.connect( self._on_export_preset) self._preset_menu.action_duplicate.triggered.connect( self._on_duplicate_preset) self._preset_menu.action_map_tracker.triggered.connect( self._on_open_map_tracker_for_preset) self._preset_menu.action_import.triggered.connect( self._on_import_preset) window.create_preset_tree.update_items() @asyncSlot() async def _do_migration(self): dialog = QtWidgets.QProgressDialog( ("Randovania changed where your presets are saved and a one-time migration is being performed.\n" "Further changes in old versions won't be migrated."), None, 0, 1, self, ) common_qt_lib.set_default_window_icon(dialog) dialog.setWindowTitle("Preset Migration") dialog.setAutoReset(False) dialog.setAutoClose(False) dialog.show() def on_update(current, target): dialog.setValue(current) dialog.setMaximum(target) await self._window_manager.preset_manager.migrate_from_old_path( on_update) self.window.create_preset_tree.update_items() dialog.setCancelButtonText("Ok") def _tab_show_event(self, event: QtGui.QShowEvent): if self._window_manager.preset_manager.should_do_migration(): QTimer.singleShot(0, self._do_migration) return self._original_show_event(event) @property def _current_preset_data(self) -> Optional[VersionedPreset]: return self.window.create_preset_tree.current_preset_data def enable_buttons_with_background_tasks(self, value: bool): self.window.stop_background_process_button.setEnabled(not value) self.window.create_generate_button.setEnabled(value) self.window.create_generate_race_button.setEnabled(value) def _add_new_preset(self, preset: VersionedPreset): with self._options as options: options.selected_preset_uuid = preset.uuid self._window_manager.preset_manager.add_new_preset(preset) self.window.create_preset_tree.update_items() self.window.create_preset_tree.select_preset(preset) @asyncSlot() async def _on_customize_preset(self): if self._logic_settings_window is not None: self._logic_settings_window.raise_() return old_preset = self._current_preset_data editor = PresetEditor(old_preset.get_preset()) self._logic_settings_window = LogicSettingsWindow( self._window_manager, editor) self._logic_settings_window.on_preset_changed( editor.create_custom_preset_with()) editor.on_changed = lambda: self._logic_settings_window.on_preset_changed( editor.create_custom_preset_with()) result = await async_dialog.execute_dialog(self._logic_settings_window) self._logic_settings_window = None if result == QtWidgets.QDialog.Accepted: self._add_new_preset( VersionedPreset.with_preset( editor.create_custom_preset_with())) def _on_delete_preset(self): self._window_manager.preset_manager.delete_preset( self._current_preset_data) index = self.window.create_preset_tree.currentIndex() self.window.create_preset_tree.update_items() self.window.create_preset_tree.setCurrentIndex(index) self._on_select_preset() def _on_view_preset_history(self): pass def _on_export_preset(self): path = common_qt_lib.prompt_user_for_preset_file(self._window_manager, new_file=True) if path is not None: self._current_preset_data.save_to_file(path) def _on_duplicate_preset(self): old_preset = self._current_preset_data self._add_new_preset( VersionedPreset.with_preset(old_preset.get_preset().fork())) def _on_open_map_tracker_for_preset(self): self._window_manager.open_map_tracker( self._current_preset_data.get_preset().configuration) def _on_import_preset(self): path = common_qt_lib.prompt_user_for_preset_file(self._window_manager, new_file=False) if path is not None: self.import_preset_file(path) def import_preset_file(self, path: Path): preset = VersionedPreset.from_file_sync(path) try: preset.get_preset() except InvalidPreset: QtWidgets.QMessageBox.critical( self._window_manager, "Error loading preset", "The file at '{}' contains an invalid preset.".format(path)) return existing_preset = self._window_manager.preset_manager.preset_for_uuid( preset.uuid) if existing_preset is not None: user_response = QtWidgets.QMessageBox.warning( self._window_manager, "Preset ID conflict", "The new preset '{}' has the same ID as existing '{}'. Do you want to overwrite it?" .format( preset.name, existing_preset.name, ), QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No | QtWidgets.QMessageBox.Cancel, QtWidgets.QMessageBox.Cancel) if user_response == QtWidgets.QMessageBox.Cancel: return elif user_response == QtWidgets.QMessageBox.No: preset = VersionedPreset.with_preset( dataclasses.replace(preset.get_preset(), uuid=uuid.uuid4())) self._add_new_preset(preset) def _on_select_preset(self): preset_data = self._current_preset_data self.on_preset_changed(preset_data) if preset_data is not None: with self._options as options: options.selected_preset_uuid = preset_data.uuid def _on_tree_context_menu(self, pos: QtCore.QPoint): item: QtWidgets.QTreeWidgetItem = self.window.create_preset_tree.itemAt( pos) preset = None if item is not None: preset = self.window.create_preset_tree.preset_for_item(item) self._preset_menu.set_preset(preset) self._preset_menu.exec_(QtGui.QCursor.pos()) # Generate seed def _generate_new_seed(self, spoiler: bool, retries: Optional[int] = None): preset = self._current_preset_data num_players = self.window.num_players_spin_box.value() self.generate_seed_from_permalink(Permalink( seed_number=random.randint(0, 2**31), spoiler=spoiler, presets={i: preset.get_preset() for i in range(num_players)}, ), retries=retries) def generate_seed_from_permalink(self, permalink: Permalink, retries: Optional[int] = None): def work(progress_update: ProgressUpdateCallable): try: layout = simplified_patcher.generate_layout( progress_update=progress_update, permalink=permalink, options=self._options, retries=retries) progress_update( f"Success! (Seed hash: {layout.shareable_hash})", 1) persist_layout(self._options.game_history_path, layout) self._window_manager.open_game_details(layout) except GenerationFailure as generate_exception: self.failure_handler.handle_failure(generate_exception) progress_update( "Generation Failure: {}".format(generate_exception), -1) if self._window_manager.is_preview_mode: print(f"Permalink: {permalink.as_base64_str}") self.run_in_background_thread(work, "Creating a seed...") def on_options_changed(self, options: Options): if not self._has_set_from_last_selected: self._has_set_from_last_selected = True preset = self._window_manager.preset_manager.preset_for_uuid( options.selected_preset_uuid) if preset is None: preset = self._window_manager.preset_manager.default_preset self.window.create_preset_tree.select_preset(preset) def on_preset_changed(self, preset: Optional[VersionedPreset]): can_generate = False if preset is None: description = "Please select a preset from the list, not a game." else: try: raw_preset = preset.get_preset() can_generate = True description = f"<p style='font-weight:600;'>{raw_preset.name}</p><p>{raw_preset.description}</p>" description += preset_describer.merge_categories( preset_describer.describe(raw_preset)) except InvalidPreset as e: logging.exception(f"Invalid preset for {preset.name}") description = ( f"Preset {preset.name} can't be used as it contains the following error:" f"\n{e.original_exception}\n" f"\nPlease open edit the preset file with id {preset.uuid} manually or delete this preset." ) self.window.create_preset_description.setText(description) for btn in [ self.window.create_generate_button, self.window.create_generate_race_button ]: btn.setEnabled(can_generate) def update_progress(self, message: str, percentage: int): self.window.progress_label.setText(message) if "Aborted" in message: percentage = 0 if percentage >= 0: self.window.progress_bar.setRange(0, 100) self.window.progress_bar.setValue(percentage) else: self.window.progress_bar.setRange(0, 0)