class FE14CharacterEditor(Ui_FE14CharacterEditor): def __init__(self, is_person=False, parent=None): super().__init__(parent) self.is_person = is_person self.module: TableModule = locator.get_scoped( "ModuleService").get_module("Characters") self.proxy_model = QSortFilterProxyModel() self.proxy_model.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive) self.proxy_model.setSourceModel(self.module.entries_model) self.characters_list_view.setModel(self.proxy_model) self.selection: Optional[PropertyContainer] = None self.character_details_form_1 = PropertyForm( self.module.element_template, category="character_description_1") self.character_details_form_contents_1.setLayout( self.character_details_form_1) self.character_details_form_2 = PropertyForm( self.module.element_template, category="character_description_2") self.character_details_form_contents_2.setLayout( self.character_details_form_2) self.character_details_form_2.fix_editor_width(100) self.stats_editor = MergedStatsEditor( ["Bases", "Growths", "Modifiers", "Penalties", "Bonuses"]) self.stats_form = PropertyForm(self.module.element_template, category="stats") self.stats_layout.addWidget(self.stats_editor) self.stats_layout.addLayout(self.stats_form) self.skills_form = PropertyForm(self.module.element_template, category="skills", sort_editors=True) self.skills_contents.setLayout(self.skills_form) self.flags_editor = MergedFlagsEditor( ["Bitflags (1)", "Bitflags (2)", "Bitflags (3)", "Bitflags (4)"], self.module.element_template) self.flags_editor_2 = MergedFlagsEditor( ["Bitflags (5)", "Bitflags (6)", "Bitflags (7)", "Bitflags (8)"], self.module.element_template) self.misc_form = PropertyForm(self.module.element_template, category="misc") self.misc_layout.addWidget(self.flags_editor) self.misc_layout.addWidget(self.flags_editor_2) self.misc_layout.addLayout(self.misc_form) self.ids_form = PropertyForm(self.module.element_template, category="ids") self.ids_tab.setLayout(self.ids_form) self.classes_form = PropertyForm(self.module.element_template, category="classes", sort_editors=True) self.classes_tab.setLayout(self.classes_form) if not self.is_person: self.dialogue_tab = DialogueEditor() self.supports_tab = QWidget() self.supports_layout = QHBoxLayout() self.supports_widget = FE14SupportWidget() self.supports_scroll = QScrollArea() self.supports_scroll_contents = QWidget() self.supports_scroll.setWidget(self.supports_scroll_contents) self.supports_scroll.setWidgetResizable(True) self.supports_layout.addWidget(self.supports_widget) self.supports_layout.addWidget(self.supports_scroll) self.supports_tab.setLayout(self.supports_layout) self.supports_form = PropertyForm(self.module.element_template, category="supports") self.supports_scroll_contents.setLayout(self.supports_form) self.tab_widget.addTab(self.supports_tab, "Supports") self.tab_widget.addTab(self.dialogue_tab, "Dialogue") self.context_menu = QMenu(self) self.context_menu.addActions( [self.action_add, self.action_remove, self.action_copy_to]) self.clear_selection_shortcut = QShortcut(QKeySequence.Cancel, self) self._install_signals() self._clear() def set_module(self, module: TableModule): self.module = module if self.module: self.proxy_model.setSourceModel(self.module.entries_model) else: self.proxy_model.setSourceModel(None) self.setEnabled(self.module is not None) self._clear() def _on_context_menu_requested(self, point: QPoint): self.context_menu.exec_(self.characters_list_view.mapToGlobal(point)) def _install_signals(self): self.characters_list_view.selectionModel().currentRowChanged.connect( self._update_selection) self.characters_list_view.customContextMenuRequested.connect( self._on_context_menu_requested) self.search_bar.textChanged.connect(self._update_filter) self.action_add.triggered.connect(self._on_add_character_triggered) self.action_remove.triggered.connect( self._on_remove_character_triggered) self.action_copy_to.triggered.connect(self._on_copy_to_triggered) self.clear_selection_shortcut.activated.connect(self._clear) if self.character_details_form_1.editors['Name'] != None: self.character_details_form_1.editors[ 'Name'].value_editor.editingFinished.connect( self._update_conversation_widget) def _clear(self): self.characters_list_view.clearSelection() self.characters_list_view.selectionModel().clearCurrentIndex() def _update_selection(self, index: QModelIndex): self.selection = self.proxy_model.data(index, QtCore.Qt.UserRole) self.portraits_tab.update_target(self.selection) self.character_details_form_1.update_target(self.selection) self.character_details_form_2.update_target(self.selection) self.stats_editor.update_target(self.selection) self.ids_form.update_target(self.selection) self.classes_form.update_target(self.selection) self.stats_form.update_target(self.selection) self.skills_form.update_target(self.selection) self.flags_editor.update_target(self.selection) self.flags_editor_2.update_target(self.selection) self.misc_form.update_target(self.selection) if not self.is_person: self.dialogue_tab.update_target(self.selection) self.supports_widget.update_target(self.selection) self.supports_form.update_target(self.selection) if self.selection: locator.get_scoped("SpriteService").get_sprite_for_character( self.selection, 0) self.action_remove.setEnabled(self.selection is not None) self.action_copy_to.setEnabled(self.selection is not None) self._update_portrait_box() def _update_portrait_box(self): portrait_service = locator.get_scoped("PortraitService") mini_portraits = portrait_service.get_sorted_portraits_for_character( self.selection, "bu") if mini_portraits: _, texture = mini_portraits[0] scene = QGraphicsScene() scene.addPixmap(QPixmap.fromImage(texture.image())) self.portrait_display.setScene(scene) else: self.portrait_display.setScene(None) def _update_filter(self): self.proxy_model.setFilterRegExp(self.search_bar.text()) def _on_add_character_triggered(self): model = self.module.entries_model model.insertRow(model.rowCount()) source = self.module.entries[0] destination = self.module.entries[-1] source.copy_to(destination) # Update any present conversation widget with the new obj list self._update_conversation_widget() def _on_remove_character_triggered(self): if self.characters_list_view.currentIndex().isValid(): model = self.module.entries_model model.removeRow(self.characters_list_view.currentIndex().row()) model.beginResetModel() model.endResetModel() # Update any present conversation widget with the new obj list self._update_conversation_widget() def _update_conversation_widget(self): for editor in self.supports_widget.service._conversation_editors: editor: FE14ConversationEditor character_list = list() [ character_list.append(child[1]) for child in self.module.children() ] editor.text_area._character_list = character_list def _on_copy_to_triggered(self): if not self.selection: return logging.info("Beginning copy to for " + self.module.name) choices = [] for i in range(0, len(self.module.entries)): choices.append( str(i + 1) + ". " + self.module.entries[i].get_display_name()) choice = QInputDialog.getItem(self, "Select Destination", "Destination", choices) if choice[1]: for i in range(0, len(choices)): if choice[0] == choices[i]: self.selection.copy_to(self.module.entries[i]) # Update any present conversation widget with the new obj list self._update_conversation_widget() else: logging.info("No choice selected for " + self.module.name + " copy to. Aborting.")
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)
class MainWindow(QMainWindow, Ui_MainWindow): def __init__(self, close_handler, driver: Driver): super().__init__() self.setupUi(self) self.driver = driver self.open_editors = {} self.close_handler = close_handler self.setWindowTitle("Paragon") self.setWindowIcon(QIcon("paragon.ico")) self.proxy_model = QSortFilterProxyModel() self.proxy_model.setSourceModel(self.driver.module_model) self.module_list_view.setModel(self.proxy_model) self.editors_list_view.setModel(self.driver.services_model) self.open_file_model = OpenFilesModel() self.file_list_view.setModel(self.open_file_model) self.search_field.textChanged.connect(self.proxy_model.setFilterRegExp) 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_close.triggered.connect(self.close) self.action_quit.triggered.connect(self.quit_application) logging.info("Opened main window.") def save(self): self.driver.save() def close(self): self.hide() self.close_handler() @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.") # First, see if the module is already open. module: Module = self.proxy_model.data(index, QtCore.Qt.UserRole) if module in self.open_editors: logging.info(module.name + " is cached. Reopening editor...") self.driver.set_module_used(module) self.open_editors[module].show() return # Next, handle common modules. if not module.unique: 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...") module = self.driver.handle_open_for_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() if module.type == "table": logging.info("Opening " + module.name + " as a TableModule.") editor = SimpleEditor(self.driver, cast(TableModule, module)) elif module.type == "object": logging.info("Opening " + module.name + " as an ObjectModule.") editor = ObjectEditor(self.driver, module) else: logging.error("Attempted to open an unsupported module type.") raise NotImplementedError self.driver.set_module_used(module) self.open_editors[module] = editor editor.show() def _on_editor_activated(self, index): model = self.driver.services_model service = model.data(index, QtCore.Qt.UserRole) service.editor.show()
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())