예제 #1
0
 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()
예제 #2
0
 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)
예제 #3
0
    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)
예제 #4
0
 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()
예제 #5
0
 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)
예제 #6
0
 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)
예제 #7
0
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()
예제 #8
0
 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))
예제 #9
0
 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()
예제 #10
0
 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))
예제 #11
0
 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()
예제 #12
0
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")
예제 #13
0
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)
예제 #14
0
 def __init__(self):
     self.error_dialog = ErrorDialog("Failed to open field data.")
     self.editors = {}
예제 #15
0
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)
예제 #16
0
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()
예제 #17
0
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)
예제 #18
0
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()
예제 #19
0
 def __init__(self):
     self.error_dialog = ErrorDialog("Failed to open message archive.")
     self.editors = {}
예제 #20
0
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())