def show(self): super().show() self.setDisabled(not self.service.load_succeeded) if not self.service.load_succeeded: self.error_dialog = ErrorDialog( "Unable to load required data. See the log for details.") self.error_dialog.show()
def __init__(self): super().__init__("Loading") self.project = None self.loading_thread: Optional[LoadingWorker] = None self.progress_dialog = None self.error_dialog = ErrorDialog("Loading failed. See the log file for details.") self.error_dialog.finished.connect(self._on_error_dialog_closed)
def _on_add_chapter_triggered(self): # Get the chapter to use as a base choices = self._create_chapter_choice_list() (choice, ok) = QInputDialog.getItem(self, "Select Base Chapter", "Base Chapter", choices) if not ok: return source_chapter = self._get_chapter_from_choice(choice, choices) # Get the desired CID. (desired_cid, ok) = QInputDialog.getText(self, "Enter a CID for the new chapter.", "CID") if not ok: return # Validate the CID. service = locator.get_scoped("ChapterService") if service.is_cid_in_use(desired_cid): self.error_dialog = ErrorDialog("The CID \"" + desired_cid + "\" is already in use.") self.error_dialog.show() return if not desired_cid.startswith("CID_"): self.error_dialog = ErrorDialog("CID must start with the \"CID_\"") self.error_dialog.show() return # Create the chapter service.create_chapter(source_chapter, desired_cid)
def _try_export_and_write(self, file_name): logging.debug("Exporting selected data to %s" % file_name) try: export_data = self.model.export_selected_items() with open(file_name, "w", encoding="utf-8") as f: json.dump(export_data, f, indent=4, ensure_ascii=False) except: logging.exception("An error occurred while exporting data.") self.error_dialog = ErrorDialog( "An error occurred while exporting. See the log for details.") self.error_dialog.show()
def show(self): super().show() self.service = locator.get_scoped("SupportsService") self.service.set_in_use() success = True try: self.service.check_support_id_validity() except: logging.exception("Support IDs are invalid.") self.error_dialog = ErrorDialog("Support IDs are invalid. This could mean an ID was out of bounds or not " "unique. See the log for details.") self.error_dialog.show() success = False self.setDisabled(not success)
def _on_import_triggered(self): file_name, ok = QFileDialog.getOpenFileName(self, "Select file.", filter="*.json") if ok: try: locator.get_scoped("Driver").import_from_json(file_name) self.statusbar.showMessage("Import succeeded!", 5000) except: logging.exception("An error occurred during importing.") self.error_dialog = ErrorDialog( "Importing failed. See the log for details.") self.error_dialog.show() self.statusbar.showMessage("Importing failed.", 5000)
class FE14ConversationPlayer(Ui_ConversationPlayer): def __init__(self, parent=None): super().__init__(parent) self.conversation_controller = ConversationController( self.conversation_widget) self.replay_button.clicked.connect(self._replay) self.next_button.clicked.connect(self._play_next) self.position = 0 self.commands: Optional[List[Command]] = None self.error_dialog = None self.clear() def set_commands(self, commands: Optional[List[Command]]): self.clear() self.commands = commands if self.commands: self.next_button.setEnabled(True) self.replay_button.setEnabled(True) self._replay() def clear(self): self.commands = None self.conversation_controller.reset() self.position = 0 self.next_button.setEnabled(False) self.replay_button.setEnabled(False) def _play_next(self): while self.commands and self.position < len( self.commands) and not self.commands[self.position].is_pause(): try: self.commands[self.position].run(self.conversation_controller) except Exception as e: self.error_dialog = ErrorDialog( "An error occured during interpreting - " + str(e)) self.error_dialog.show() self.clear() return self.position += 1 self.conversation_controller.dump() self.position += 1 self.next_button.setEnabled(self.commands and self.position < len(self.commands)) def _replay(self): self.conversation_controller.reset() self.position = 0 self._play_next()
def _play_next(self): while self.commands and self.position < len( self.commands) and not self.commands[self.position].is_pause(): try: self.commands[self.position].run(self.conversation_controller) except Exception as e: self.error_dialog = ErrorDialog( "An error occured during interpreting - " + str(e)) self.error_dialog.show() self.clear() return self.position += 1 self.conversation_controller.dump() self.position += 1 self.next_button.setEnabled(self.commands and self.position < len(self.commands))
def _on_module_activated(self, index): logging.info("Module " + str(index.row()) + " activated.") module_model = locator.get_scoped("ModuleService").get_module_model() index = self.proxy_model.mapToSource(index) item = module_model.itemFromIndex(index) if item.data(): module = item.data() try: self._open_module_editor(module) except: logging.exception("Failed to open editor for module %s" % module.name) self.error_dialog = ErrorDialog( "Unable to open module %s. See the log for details." % module.name) self.error_dialog.show()
def _add_group(self): if self.active and self.dispos_model: (faction_name, ok) = QInputDialog.getText(self.view, "Enter a group name.", "Name:") if ok: if self.dispos_model.is_faction_name_in_use(faction_name): self.error_dialog = ErrorDialog( "Faction name is already in use.") self.error_dialog.show() return else: self.dispos_model.add_faction(faction_name) faction = self.dispos_model.dispos.factions[-1] self.dispos_model.undo_stack.push_action( AddGroupAction(self, faction))
def _on_add_set_triggered(self): (desired_name, ok) = QInputDialog.getText(self, "Enter a unique name for the voice set.", "Name") if not ok: return try: self.voice_set_model.create_voice_set(desired_name) except NameError: self.error_dialog = ErrorDialog( "Voice set name %s is already in use." % desired_name) self.error_dialog.show() except: logging.exception( "Unknown error when adding voice set to IndirectSound.") self.error_dialog = ErrorDialog( "An unknown error occurred. See the log for details.") self.error_dialog.show()
class LoadingState(State): def __init__(self): super().__init__("Loading") self.project = None self.loading_thread: Optional[LoadingWorker] = None self.progress_dialog = None self.error_dialog = ErrorDialog("Loading failed. See the log file for details.") self.error_dialog.finished.connect(self._on_error_dialog_closed) def act(self): if not self.project: logging.fatal("Entered loading without locating a project.") sys.exit(1) logging.info("Entered Loading state.") self.progress_dialog = QProgressDialog("Loading modules...", "Quit", 0, 0) self.progress_dialog.setWindowTitle("Paragon - Loading") self.progress_dialog.setWindowIcon(QIcon("paragon.ico")) self.progress_dialog.setAutoClose(False) self.progress_dialog.hide() self.progress_dialog.show() self.loading_thread = LoadingWorker(self.project) self.loading_thread.over.connect(self._on_loading_success) self.loading_thread.failed.connect(self._on_loading_failure) self.loading_thread.start() def _on_loading_success(self): self.progress_dialog.hide() locator.get_static("StateMachine").transition("Main") def _on_loading_failure(self): self.progress_dialog.hide() self.error_dialog.exec() @staticmethod def _on_error_dialog_closed(_result): locator.get_static("StateMachine").transition("SelectProject")
class FE14SupportEditor(QWidget, Ui_support_editor): def __init__(self): super().__init__() self.setupUi(self) self.pushButton_2.setEnabled(False) self.pushButton_3.setEnabled(False) self.comboBox.setEnabled(False) self.setWindowTitle("Support Editor") self.setWindowIcon(QIcon("paragon.ico")) self.error_dialog = None module_service = locator.get_scoped("ModuleService") self.service = None self.current_character = None self.current_supports = None self.current_support = None self.model = module_service.get_module("Characters").entries_model self.proxy_model = QSortFilterProxyModel(self) self.proxy_model.setSourceModel(self.model) self.proxy_model.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive) self.characters_list_view.setModel(self.proxy_model) self.characters_list_view.selectionModel().currentRowChanged.connect(self._update_selection) self.listWidget.selectionModel().currentRowChanged.connect(self._on_target_character_changed) self.listWidget_2.selectionModel().currentRowChanged.connect(self._update_support_selection) self.lineEdit.textChanged.connect(self._update_filter) self.pushButton_2.clicked.connect(self._on_add_support_pressed) self.pushButton_3.clicked.connect(self._on_remove_support_pressed) self.comboBox.currentIndexChanged.connect(self._on_support_type_changed) def show(self): super().show() self.service = locator.get_scoped("SupportsService") self.service.set_in_use() success = True try: self.service.check_support_id_validity() except: logging.exception("Support IDs are invalid.") self.error_dialog = ErrorDialog("Support IDs are invalid. This could mean an ID was out of bounds or not " "unique. See the log for details.") self.error_dialog.show() success = False self.setDisabled(not success) def _update_filter(self): self.proxy_model.setFilterRegExp(self.lineEdit.text()) def _update_selection(self, index: QtCore.QModelIndex): if index.isValid(): character = self.proxy_model.data(index, QtCore.Qt.UserRole) self._refresh_lists(character) self.current_character = character def _refresh_lists(self, character): self._update_supports_list(character) self._update_add_list(character) self.current_support = None self.comboBox.setEnabled(False) self.pushButton_2.setEnabled(False) self.pushButton_3.setEnabled(False) def _update_add_list(self, character): supported_characters = self._create_supported_characters_set(character) module_service = locator.get_scoped("ModuleService") self.listWidget.clear() characters = module_service.get_module("Characters").entries for target_character in characters: if target_character["PID"] not in supported_characters: model_index = self._get_model_index_of_character(target_character) display_name = self.model.data(model_index, QtCore.Qt.DisplayRole) item = QListWidgetItem(display_name) item.setData(QtCore.Qt.UserRole, target_character) self.listWidget.addItem(item) # Dict is not hashable. PIDs should be unique, so we'll use those instead. # Might be able to use IDs instead. def _create_supported_characters_set(self, character): supports = self.service.get_supports_for_character(character) result = set() for support in supports: result.add(support.character["PID"]) return result def _update_supports_list(self, character): supports = self.service.get_supports_for_character(character) self.listWidget_2.clear() for support in supports: model_index = self._get_model_index_of_character(support.character) display_name = self.model.data(model_index, QtCore.Qt.DisplayRole) item = QListWidgetItem(display_name) item.setData(QtCore.Qt.UserRole, support) self.listWidget_2.addItem(item) self.current_supports = supports def _get_model_index_of_character(self, character): module_service = locator.get_scoped("ModuleService") entries = module_service.get_module("Characters").entries for i in range(0, len(entries)): if entries[i] == character: return self.model.index(i) return QModelIndex() def _update_support_selection(self, index): if not index.isValid() or not self.current_supports: return self.current_support = self.current_supports[index.row()] index = SUPPORT_TYPE_TO_INDEX[self.current_support.support_type] self.comboBox.setCurrentIndex(index) self.comboBox.setEnabled(True) self.pushButton_3.setEnabled(True) def _on_support_type_changed(self, index): if not self.current_character or not self.current_support: return support_type = INDEX_TO_SUPPORT_TYPE[index] self.service.set_support_type(self.current_character, self.current_support, support_type) def _on_target_character_changed(self): self.pushButton_2.setEnabled(self.listWidget.currentIndex().isValid()) def _on_add_support_pressed(self): if not self.current_character or not self.listWidget.currentIndex().isValid(): return other_character = self.listWidget.currentItem().data(QtCore.Qt.UserRole) support_type = INDEX_TO_SUPPORT_TYPE[0] # Default to romantic. self.service.add_support_between_characters(self.current_character, other_character, support_type) self._refresh_lists(self.current_character) def _on_remove_support_pressed(self): if not self.current_character or not self.current_support: return self.service.remove_support(self.current_character, self.current_support) self._refresh_lists(self.current_character)
def __init__(self): self.error_dialog = ErrorDialog("Failed to open field data.") self.editors = {}
class ExportDialog(QWidget): def __init__(self, parent=None): super().__init__(parent) self.model = ExportChangesModel() self.error_dialog = None self.setWindowIcon(QIcon("paragon.ico")) self.setWindowTitle("Export") main_layout = QVBoxLayout(self) self.export_button = QPushButton() self.export_button.setText("Export") self.main_view = QTreeView() self.main_view.setModel(self.model) self.main_view.setHeaderHidden(True) self.main_view.setEditTriggers( QtWidgets.QAbstractItemView.NoEditTriggers) self.remember_selections_checkbox = QCheckBox() self.remember_selections_checkbox.setText("Remember selections.") main_layout.addWidget(self.export_button) main_layout.addWidget(self.main_view) main_layout.addWidget(self.remember_selections_checkbox) self.setLayout(main_layout) self.main_view.expanded.connect(self._on_item_expanded) self.export_button.clicked.connect(self._on_export_triggered) self.remember_selections_checkbox.stateChanged.connect( self._on_remember_selections_state_changed) self._set_remember_checkbox_state_from_settings() def _set_remember_checkbox_state_from_settings(self): settings_service = locator.get_static("SettingsService") if settings_service.get_remember_exports(): self.remember_selections_checkbox.setCheckState(QtCore.Qt.Checked) @staticmethod def _on_remember_selections_state_changed(state: int): should_remember = state == QtCore.Qt.Checked locator.get_static("SettingsService").set_remember_exports( should_remember) def closeEvent(self, event: QtGui.QCloseEvent): self.model.save_selected_items_tree() event.accept() def _on_export_triggered(self): file_name, ok = QFileDialog.getSaveFileName( self, caption="Select Changes File Destination", filter="*.json") if ok: self._try_export_and_write(file_name) def _try_export_and_write(self, file_name): logging.debug("Exporting selected data to %s" % file_name) try: export_data = self.model.export_selected_items() with open(file_name, "w", encoding="utf-8") as f: json.dump(export_data, f, indent=4, ensure_ascii=False) except: logging.exception("An error occurred while exporting data.") self.error_dialog = ErrorDialog( "An error occurred while exporting. See the log for details.") self.error_dialog.show() def _on_item_expanded(self, index: QModelIndex): self.model.update_data_for_index(index)
class MainWindow(QMainWindow, Ui_MainWindow): def __init__(self): super().__init__() self.setupUi(self) self.open_editors = {} self.proxy_model = None self.open_file_model = None self.error_dialog = None self.export_dialog = None self.theme_action_group = QActionGroup(self) self.theme_menu = None self.theme_info_dialog = self._create_theme_info_dialog() self.setWindowTitle("Paragon") self.setWindowIcon(QIcon("paragon.ico")) self._set_view_models() self._install_signal_handlers() self._populate_themes_menu() logging.info("Opened main window.") def _on_export_triggered(self): self.export_dialog = ExportDialog() self.export_dialog.show() def _on_import_triggered(self): file_name, ok = QFileDialog.getOpenFileName(self, "Select file.", filter="*.json") if ok: try: locator.get_scoped("Driver").import_from_json(file_name) self.statusbar.showMessage("Import succeeded!", 5000) except: logging.exception("An error occurred during importing.") self.error_dialog = ErrorDialog( "Importing failed. See the log for details.") self.error_dialog.show() self.statusbar.showMessage("Importing failed.", 5000) @staticmethod def _create_theme_info_dialog(): theme_info_dialog = QMessageBox() theme_info_dialog.setText( "This theme will be used the next time you run Paragon.") theme_info_dialog.setWindowTitle("Paragon") theme_info_dialog.setWindowIcon(QIcon("paragon.ico")) return theme_info_dialog def closeEvent(self, event: QtGui.QCloseEvent): locator.get_static("SettingsService").save_settings() event.accept() def _populate_themes_menu(self): self.theme_menu = self.menuOptions.addMenu("Theme") current_theme = locator.get_static("SettingsService").get_theme() for theme in QStyleFactory.keys(): action = self.theme_menu.addAction(theme) action.setCheckable(True) if theme == current_theme: action.setChecked(True) action.setActionGroup(self.theme_action_group) action.changed.connect( lambda a=action, t=theme: self._on_theme_changed(a, t)) def _set_view_models(self): module_service = locator.get_scoped("ModuleService") dedicated_editors_service = locator.get_scoped( "DedicatedEditorsService") self.proxy_model = ModuleFilterModel() self.open_file_model = OpenFilesModel() self.proxy_model.setSourceModel(module_service.get_module_model()) self.module_list_view.setModel(self.proxy_model) self.editors_list_view.setModel( dedicated_editors_service.get_dedicated_editors_model()) self.file_list_view.setModel(self.open_file_model) self.module_list_view.setHeaderHidden(True) self.module_list_view.setEditTriggers( QtWidgets.QAbstractItemView.NoEditTriggers) def _install_signal_handlers(self): self.search_field.textChanged.connect(self._update_filter) self.module_list_view.activated.connect(self._on_module_activated) self.editors_list_view.activated.connect(self._on_editor_activated) self.file_list_view.selectionModel().currentRowChanged.connect( self._on_file_list_selection_change) self.close_button.clicked.connect(self._on_close_file_pressed) self.action_save.triggered.connect(self.save) self.action_reload.triggered.connect(self.reload_project) self.action_close.triggered.connect(self.close) self.action_quit.triggered.connect(self.quit_application) self.action_export.triggered.connect(self._on_export_triggered) self.action_import.triggered.connect(self._on_import_triggered) def _update_filter(self, new_filter): self.proxy_model.setFilterRegExp(new_filter.lower()) def save(self): driver = locator.get_scoped("Driver") if driver.save(): self.statusbar.showMessage("Save succeeded!", 5000) else: self.statusbar.showMessage("Save failed. See the log for details.", 5000) def close(self): self.proxy_model.setSourceModel(None) self.hide() state_machine = locator.get_static("StateMachine") state_machine.transition("SelectProject") def reload_project(self): logging.info("Reload project triggered.") self.proxy_model.setSourceModel(None) self.hide() state_machine = locator.get_static("StateMachine") state_machine.transition("Loading") @staticmethod def quit_application(): exit(0) def _on_file_list_selection_change(self, index): self.close_button.setEnabled( self.open_file_model.can_close(index.row())) def _on_close_file_pressed(self): self.open_file_model.close(self.file_list_view.currentIndex().row()) def _on_module_activated(self, index): logging.info("Module " + str(index.row()) + " activated.") module_model = locator.get_scoped("ModuleService").get_module_model() index = self.proxy_model.mapToSource(index) item = module_model.itemFromIndex(index) if item.data(): module = item.data() try: self._open_module_editor(module) except: logging.exception("Failed to open editor for module %s" % module.name) self.error_dialog = ErrorDialog( "Unable to open module %s. See the log for details." % module.name) self.error_dialog.show() def _open_module_editor(self, module: Module): if module in self.open_editors: logging.info(module.name + " is cached. Reopening editor...") editor = self.open_editors[module] elif not module.unique: module = self._handle_common_module_open(module) if not module: return editor = self._get_editor_for_module(module) else: editor = self._get_editor_for_module(module) module_service = locator.get_scoped("ModuleService") module_service.set_module_in_use(module) self.open_editors[module] = editor editor.show() def _on_theme_changed(self, action: QAction, new_theme: str): if action.isChecked(): locator.get_static("SettingsService").set_theme(new_theme) self.theme_info_dialog.exec_() def _get_editor_for_module(self, module): if module.type == "table": logging.info("Opening " + module.name + " as a TableModule.") editor = SimpleEditor(cast(TableModule, module)) elif module.type == "object": logging.info("Opening " + module.name + " as an ObjectModule.") editor = ObjectEditor(module) else: logging.error("Attempted to open an unsupported module type.") raise NotImplementedError return editor def _handle_common_module_open(self, module): logging.info(module.name + " is a common module. Prompting for a file...") target_file = QFileDialog.getOpenFileName(self) if not target_file[0]: logging.info("No file selected - operation aborted.") return logging.info("File selected. Opening common module...") common_module_service = locator.get_scoped("CommonModuleService") module = common_module_service.open_common_module( module, target_file[0]) # Hack to keep the display for the open file model consistent. self.open_file_model.beginResetModel() self.open_file_model.endResetModel() return module @staticmethod def _on_editor_activated(index): dedicated_editors_service = locator.get_scoped( "DedicatedEditorsService") model = dedicated_editors_service.get_dedicated_editors_model() service = model.data(index, QtCore.Qt.UserRole) editor = service.get_editor() if editor: editor.show()
class FE14MapEditorDisposController: def __init__(self, map_editor: FE14MapEditor): self.view = map_editor self.error_dialog = None self.copied_spawn = None self.chapter_data: Optional[ChapterData] = None self.dispos_model: Optional[DisposModel] = None self.current_faction: Optional[Faction] = None self.active = True self.selection_change_in_progress = False self.set_active(self.active) self.view.coordinate_type_label.setText("Coordinate Type: " + self.view.grid.coordinate_key) self.view.add_group_action.triggered.connect(self._add_group) self.view.delete_group_action.triggered.connect(self._delete_group) self.view.delete_spawn_action.triggered.connect(self._delete_spawn) self.view.add_spawn_action.triggered.connect(self._add_spawn_to_group) self.view.grid.focused_spawn_changed.connect( self._update_spawn_selection) self.view.grid.spawn_location_changed.connect( self.update_active_spawn_position_from_grid_change) self.view.toggle_coordinate_type_action.triggered.connect( self._toggle_coordinate_type) self.view.copy_spawn_action.triggered.connect(self._copy_spawn) self.view.paste_spawn_action.triggered.connect(self._paste_spawn) self.view.undo_action.triggered.connect(self._undo) self.view.redo_action.triggered.connect(self._redo) self.view.add_item_shortcut.activated.connect( self._on_add_shortcut_pressed) self.view.delete_shortcut.activated.connect( self._on_delete_shortcut_pressed) self.view.deselect_shortcut.activated.connect(self._deselect) self.view.refresh_action.triggered.connect(self._refresh) spawn_form = self.view.spawn_pane.form spawn_form.editors["PID"].editingFinished.connect( self._on_important_spawn_field_changed) spawn_form.editors["Team"].currentIndexChanged.connect( self._on_important_spawn_field_changed) spawn_form.editors["Coordinate (1)"].set_disable_write_back(True) spawn_form.editors["Coordinate (1)"].position_changed.connect( self._on_coordinate_1_field_changed) spawn_form.editors["Coordinate (2)"].set_disable_write_back(True) spawn_form.editors["Coordinate (2)"].position_changed.connect( self._on_coordinate_2_field_changed) def update_chapter_data(self, chapter_data: Optional[ChapterData]): self.chapter_data = chapter_data if self.dispos_model: self.dispos_model.undo_stack.stack_state_changed.disconnect() self.dispos_model = chapter_data.dispos_model if chapter_data else None if self.dispos_model: self.dispos_model.undo_stack.stack_state_changed.connect( self._update_undo_redo_actions) self.update_selection(QModelIndex()) self.set_active(self.dispos_model is not None) self._update_undo_redo_actions() def update_selection(self, index: QModelIndex): if self.dispos_model and index.isValid(): data = self.dispos_model.data(index, QtCore.Qt.UserRole) if isinstance(data, Faction): self._toggle_spawn_actions(False) self._toggle_faction_actions(True) self.view.grid.clear_selection() self.view.spawn_pane.form.update_target(None) self.current_faction = data else: self._update_spawn_selection(data) if data: self.view.grid.select_spawn(data) else: self.view.spawn_pane.update_target(None) self.view.grid.clear_selection() self.current_faction = None def _update_spawn_selection(self, spawn: Optional[PropertyContainer]): self.selection_change_in_progress = True self.current_faction = self.dispos_model.get_faction_from_spawn( spawn) if spawn else None self._toggle_faction_actions(spawn is not None) self._toggle_spawn_actions(spawn is not None) self.view.spawn_pane.update_target(spawn) self.view.model_view.selectionModel().select( self.dispos_model.get_index_from_spawn(spawn), QItemSelectionModel.SelectCurrent) faction_index = self.dispos_model.get_index_from_faction( self.current_faction) if faction_index and faction_index.isValid(): self.view.model_view.expand(faction_index) self.selection_change_in_progress = False def set_active(self, active: bool): self.active = active and self.dispos_model if active: self.enable_actions_post_switch() self.view.show_spawn_pane(self.dispos_model) else: self.disable_all_actions() self.current_faction = None def enable_actions_post_switch(self): self.disable_all_actions() self.view.toggle_coordinate_type_action.setEnabled(True) self.view.add_group_action.setEnabled(True) self._update_undo_redo_actions() def disable_all_actions(self): self.view.add_group_action.setEnabled(False) self.view.delete_group_action.setEnabled(False) self.view.add_spawn_action.setEnabled(False) self.view.delete_spawn_action.setEnabled(False) self.view.toggle_coordinate_type_action.setEnabled(False) self.view.undo_action.setEnabled(False) self.view.redo_action.setEnabled(False) self.view.copy_spawn_action.setEnabled(False) self.view.paste_spawn_action.setEnabled(False) def _toggle_faction_actions(self, on: bool): self.view.add_spawn_action.setEnabled(on) self.view.delete_group_action.setEnabled(on) self.view.paste_spawn_action.setEnabled( on and self.copied_spawn is not None) def _toggle_spawn_actions(self, on: bool): self.view.delete_spawn_action.setEnabled(on) self.view.copy_spawn_action.setEnabled(on) def _add_group(self): if self.active and self.dispos_model: (faction_name, ok) = QInputDialog.getText(self.view, "Enter a group name.", "Name:") if ok: if self.dispos_model.is_faction_name_in_use(faction_name): self.error_dialog = ErrorDialog( "Faction name is already in use.") self.error_dialog.show() return else: self.dispos_model.add_faction(faction_name) faction = self.dispos_model.dispos.factions[-1] self.dispos_model.undo_stack.push_action( AddGroupAction(self, faction)) def _delete_group(self): if self.active and self.dispos_model and self.current_faction: self.dispos_model.undo_stack.push_action( DeleteGroupAction(self, self.current_faction)) self.dispos_model.delete_faction(self.current_faction) self.view.grid.set_chapter_data( self.chapter_data) # Force a refresh. self.view.spawn_pane.update_target(None) def _add_spawn_to_group(self): if self.active and self.dispos_model and self.current_faction: self.dispos_model.add_spawn_to_faction(self.current_faction) spawn = self.current_faction.spawns[-1] self.dispos_model.undo_stack.push_action( AddSpawnAction(self, self.current_faction, spawn)) self.view.grid.add_spawn_to_map(spawn) def _delete_spawn(self): if self.active and self.dispos_model and self.current_faction: spawn = self.view.grid.selected_spawn self.dispos_model.undo_stack.push_action( DeleteSpawnAction(self, self.current_faction, spawn)) self.dispos_model.delete_spawn(spawn) self.view.grid.delete_selected_spawn() self._deselect() def _toggle_coordinate_type(self): if self.dispos_model: self.view.grid.toggle_coordinate_key() self.update_selection(QModelIndex()) def update_active_spawn_position_from_grid_change(self, old_position, new_position): if not self.view.grid.selected_spawn: return if old_position[0] == new_position[0] and old_position[ 1] == new_position[1]: return spawn = self.view.grid.selected_spawn coordinate_key = self.view.grid.coordinate_key action = MoveSpawnAction(spawn, old_position, new_position, coordinate_key, self) self.dispos_model.undo_stack.push_action(action) unsigned_new_position = [ self._signed_to_unsigned(new_position[0]), self._signed_to_unsigned(new_position[1]) ] spawn[coordinate_key].value = unsigned_new_position self.view.spawn_pane.update_coordinate_of_target(coordinate_key, spawn) @staticmethod def _signed_to_unsigned(value): packed = ctypes.c_ubyte(value) return packed.value def _undo(self): if not self.active or not self.dispos_model or not self.dispos_model.undo_stack.can_undo( ): return undo_stack = self.dispos_model.undo_stack undo_stack.undo() self._update_undo_redo_actions() def _redo(self): if not self.active or not self.dispos_model or not self.dispos_model.undo_stack.can_redo( ): return undo_stack = self.dispos_model.undo_stack undo_stack.redo() self._update_undo_redo_actions() def _copy_spawn(self): if self.active and self.current_faction: spawn = self.view.grid.selected_spawn if spawn: self.copied_spawn = spawn.duplicate() self.view.paste_spawn_action.setEnabled(True) def _paste_spawn(self): if not self.dispos_model or not self.copied_spawn: return elif self.view.grid.selected_spawn: target_spawn = self.view.grid.selected_spawn self.dispos_model.undo_stack.push_action( PasteSpawnAction(self, target_spawn, self.copied_spawn)) self.dispos_model.copy_spawn_and_ignore_coordinates( self.copied_spawn, target_spawn) self.view.grid.refresh_cell_from_spawn(target_spawn) self.view.spawn_pane.update_target(target_spawn) elif self.current_faction: self.dispos_model.add_spawn_to_faction( self.current_faction, self.copied_spawn.duplicate()) spawn = self.current_faction.spawns[-1] self.dispos_model.undo_stack.push_action( AddSpawnAction(self, self.current_faction, spawn)) self.view.grid.add_spawn_to_map(spawn) def _on_important_spawn_field_changed(self): spawn = self.view.grid.selected_spawn if spawn: self.dispos_model.refresh_spawn(spawn) self.view.grid.refresh_cell_from_spawn(spawn) def _on_coordinate_1_field_changed(self, row, col): grid = self.view.grid if grid.selected_spawn and not self.selection_change_in_progress: grid.update_focused_spawn_position([row, col], "Coordinate (1)") def _on_coordinate_2_field_changed(self, row, col): grid = self.view.grid if grid.selected_spawn and not self.selection_change_in_progress: grid.update_focused_spawn_position([row, col], "Coordinate (2)") def _on_add_shortcut_pressed(self): if not self.active: return if self.current_faction: self._add_spawn_to_group() else: self._add_group() def _on_delete_shortcut_pressed(self): if not self.active: return if self.view.grid.selected_spawn: self._delete_spawn() else: self._delete_group() def _deselect(self): if self.active: self.view.model_view.setCurrentIndex(QModelIndex()) self.update_selection(QModelIndex()) self.view.grid.clear_selection() def _update_undo_redo_actions(self): if self.dispos_model: self.view.undo_action.setEnabled( self.active and self.dispos_model.undo_stack.can_undo()) self.view.redo_action.setEnabled( self.active and self.dispos_model.undo_stack.can_redo()) def _refresh(self): if self.chapter_data: self._deselect() self.selection_change_in_progress = False self.view.grid.set_chapter_data(self.chapter_data) self.view.status_bar.showMessage("Refreshed map editor.", 5000)
class FE14SoundEditor(QWidget, Ui_sound_editor): def __init__(self): super().__init__() self.setupUi(self) self.setWindowTitle("Voice Set Editor") self.setWindowIcon(QIcon("paragon.ico")) self.service = locator.get_scoped("SoundService") self.voice_set_model = self.service.get_voice_set_model() self.error_dialog = None self.form = PropertyForm(self.service.template) self.form_widget = QWidget() self.form_widget.setLayout(self.form) self.horizontalLayout_3.addWidget(self.form_widget) self.proxy_model = QtCore.QSortFilterProxyModel() self.proxy_model.setSourceModel(self.voice_set_model) self.proxy_model.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive) self.sets_menu = QMenu(self) self.add_set_action = QAction("Add Voice Set") self.remove_set_action = QAction("Remove Voice Set") self.remove_set_action.setDisabled(True) self.sets_menu.addActions( [self.add_set_action, self.remove_set_action]) self.sets_list_view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.sets_list_view.customContextMenuRequested.connect( self._on_sets_menu_requested) self.entries_menu = QMenu(self) self.entries_menu.setDisabled(True) self.add_entry_action = QAction("Add Entry") self.remove_entry_action = QAction("Remove Entry") self.copy_tag_action = QAction("Copy Tag to All Entries") self.entries_menu.addActions([ self.add_entry_action, self.remove_entry_action, self.copy_tag_action ]) self.sounds_list_view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.sounds_list_view.customContextMenuRequested.connect( self._on_entries_menu_requested) self.sets_list_view.setModel(self.proxy_model) self.sets_list_view.selectionModel().currentRowChanged.connect( self._update_set_selection) self.search_bar.textChanged.connect(self._update_filter) self.add_set_action.triggered.connect(self._on_add_set_triggered) self.remove_set_action.triggered.connect(self._on_remove_set_triggered) self.add_entry_action.triggered.connect(self._on_add_entry_triggered) self.remove_entry_action.triggered.connect( self._on_remove_entry_triggered) self.copy_tag_action.triggered.connect(self._on_copy_tags_triggered) self.form.editors["Name"].editingFinished.connect( self._write_back_entry) self.form.editors["Tag"].editingFinished.connect( self._write_back_entry) self.selected_voice_set: Optional[VoiceSetEntriesModel] = None self.selected_voice_set_entry: Optional[PropertyContainer] = None self.service.set_in_use() self._update_set_selection(QModelIndex()) def show(self): super().show() self.setDisabled(not self.service.load_succeeded) if not self.service.load_succeeded: self.error_dialog = ErrorDialog( "Unable to load required data. See the log for details.") self.error_dialog.show() def _update_filter(self): self.proxy_model.setFilterRegExp(self.search_bar.text()) def _on_entries_menu_requested(self, point: QPoint): self.entries_menu.exec_(self.sounds_list_view.mapToGlobal(point)) def _on_sets_menu_requested(self, point: QPoint): self.sets_menu.exec_(self.sets_list_view.mapToGlobal(point)) def _update_set_selection(self, index: QtCore.QModelIndex): if self.selected_voice_set: self.sounds_list_view.selectionModel( ).currentRowChanged.disconnect() self.selected_voice_set = self.proxy_model.data( index, QtCore.Qt.UserRole) self.sounds_list_view.setModel(self.selected_voice_set) if self.selected_voice_set: self.sounds_list_view.selectionModel().currentRowChanged.connect( self._update_sound_selection) self.entries_menu.setDisabled(False) self.remove_set_action.setDisabled(False) else: self.entries_menu.setDisabled(True) self.remove_set_action.setDisabled(True) self.form_widget.setDisabled(True) self.remove_entry_action.setDisabled(True) self.copy_tag_action.setDisabled(True) self.selected_voice_set_entry = None self.form.update_target(self.selected_voice_set_entry) def _update_sound_selection(self, index: QtCore.QModelIndex): self.selected_voice_set_entry = self.selected_voice_set.data( index, QtCore.Qt.UserRole) self.form_widget.setDisabled(self.selected_voice_set_entry is None) self.remove_entry_action.setDisabled( self.selected_voice_set_entry is None) self.copy_tag_action.setDisabled(self.selected_voice_set_entry is None) self.form.update_target(self.selected_voice_set_entry) def _write_back_entry(self): if self.selected_voice_set and self.selected_voice_set_entry: self.selected_voice_set.save_entry(self.selected_voice_set_entry) def _on_add_entry_triggered(self): if self.selected_voice_set: self.selected_voice_set.insertRow( self.selected_voice_set.rowCount()) def _on_remove_entry_triggered(self): if self.selected_voice_set and self.selected_voice_set_entry: self.selected_voice_set.remove_entry(self.selected_voice_set_entry) self.sounds_list_view.selectionModel().clearCurrentIndex() def _on_copy_tags_triggered(self): if self.selected_voice_set and self.selected_voice_set_entry: self.selected_voice_set.synchronize_tags( self.selected_voice_set_entry) def _on_add_set_triggered(self): (desired_name, ok) = QInputDialog.getText(self, "Enter a unique name for the voice set.", "Name") if not ok: return try: self.voice_set_model.create_voice_set(desired_name) except NameError: self.error_dialog = ErrorDialog( "Voice set name %s is already in use." % desired_name) self.error_dialog.show() except: logging.exception( "Unknown error when adding voice set to IndirectSound.") self.error_dialog = ErrorDialog( "An unknown error occurred. See the log for details.") self.error_dialog.show() def _on_remove_set_triggered(self): if self.selected_voice_set: self.voice_set_model.remove_voice_set( self.selected_voice_set.voice_set_label) self.sets_list_view.clearSelection()
def __init__(self): self.error_dialog = ErrorDialog("Failed to open message archive.") self.editors = {}
class FE14ChapterEditor(Ui_FE14ChapterEditor): def __init__(self): super().__init__() self.setWindowTitle("Chapter Editor") self.setWindowIcon(QIcon("paragon.ico")) self.error_dialog = None module_service = locator.get_scoped("ModuleService") self.chapter_module = module_service.get_module("Chapters") self.proxy_model = QSortFilterProxyModel() self.proxy_model.setSourceModel(self.chapter_module.entries_model) self.proxy_model.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive) self.chapter_list_view.setModel(self.proxy_model) self.chapter_search_bar.textChanged.connect(self._update_filter) self.chapter_list_view.selectionModel().currentRowChanged.connect( self._update_selection) self.add_chapter_action.triggered.connect( self._on_add_chapter_triggered) self.hide_selector_action.triggered.connect( self._on_toggle_selector_triggered) self._update_selection(QModelIndex()) def _update_filter(self): self.proxy_model.setFilterRegExp(self.search_field.text()) def _update_selection(self, index): service = locator.get_scoped("ChapterService") data = self.proxy_model.data(index, QtCore.Qt.UserRole) chapter_data: Optional[ ChapterData] = service.get_chapter_data_from_chapter( data) if data else None person_module = chapter_data.person if chapter_data else None message_archive = chapter_data.conversation_data if chapter_data else None self.config_tab.update_chapter_data(chapter_data) self.map_tab.update_chapter_data(chapter_data) self.characters_tab.set_module(person_module) self.conversation_tab.set_archive(message_archive) def _on_add_chapter_triggered(self): # Get the chapter to use as a base choices = self._create_chapter_choice_list() (choice, ok) = QInputDialog.getItem(self, "Select Base Chapter", "Base Chapter", choices) if not ok: return source_chapter = self._get_chapter_from_choice(choice, choices) # Get the desired CID. (desired_cid, ok) = QInputDialog.getText(self, "Enter a CID for the new chapter.", "CID") if not ok: return # Validate the CID. service = locator.get_scoped("ChapterService") if service.is_cid_in_use(desired_cid): self.error_dialog = ErrorDialog("The CID \"" + desired_cid + "\" is already in use.") self.error_dialog.show() return if not desired_cid.startswith("CID_"): self.error_dialog = ErrorDialog("CID must start with the \"CID_\"") self.error_dialog.show() return # Create the chapter service.create_chapter(source_chapter, desired_cid) def _create_chapter_choice_list(self): choices = [] for i in range(0, len(self.chapter_module.entries)): chapter = self.chapter_module.entries[i] cid = chapter["CID"].value choices.append(str(i) + ". " + cid) return choices def _get_chapter_from_choice(self, choice, choices_list): for i in range(0, len(choices_list)): if choice == choices_list[i]: return self.chapter_module.entries[i] raise ValueError def _on_toggle_selector_triggered(self): self.selector_widget.setVisible(not self.selector_widget.isVisible()) self.visual_splitter.setVisible(not self.visual_splitter.isVisible())