"""Settings, mod export and mod import dialogs.""" import os from PySide6.QtCore import Qt from PySide6.QtWidgets import (QCheckBox, QComboBox, QDialog, QDialogButtonBox, QFileDialog, QFormLayout, QHBoxLayout, QLabel, QLineEdit, QListWidget, QListWidgetItem, QPlainTextEdit, QPushButton, QSlider, QTreeWidget, QTreeWidgetItem, QVBoxLayout) from ackit.game import find_installs, is_game_dir from .audioio import output_devices def pick_game_dir(parent, start=""): """Folder chooser that explains what to pick (also Proton/Linux paths).""" d = QFileDialog.getExistingDirectory( parent, "Select the Batman: Arkham City folder (contains BmGame and Binaries)", start or os.path.expanduser("~")) return d or None class SettingsDialog(QDialog): def __init__(self, settings, parent=None): super().__init__(parent) self.setWindowTitle("Settings") self.settings = dict(settings) f = QFormLayout(self) self.game = QLineEdit(settings.get("game_dir") or "") b = QPushButton("Browse…") b.clicked.connect(self._browse_game) row = QHBoxLayout() row.addWidget(self.game, 1) row.addWidget(b) found = find_installs() hint = QLabel("Found via Steam: " + ("; ".join(found) if found else "none (Linux/Proton: " "~/.steam/steam/steamapps/common/Batman Arkham City GOTY)")) hint.setWordWrap(True) f.addRow("Game folder", row) f.addRow("", hint) self.store = QLineEdit(settings.get("store_root") or "") self.store.setPlaceholderText("default: in your user data folder") b2 = QPushButton("Browse…") b2.clicked.connect(lambda: self._browse(self.store)) row2 = QHBoxLayout() row2.addWidget(self.store, 1) row2.addWidget(b2) f.addRow("Originals store", row2) self.full = QCheckBox("Keep one full copy of each audio archive before its first change (safest)") self.full.setChecked(settings.get("full_backup", True)) f.addRow("", self.full) self.out = QComboBox() self.out.addItem("Default output") for d in output_devices(): self.out.addItem(d.description()) if settings.get("output_device"): i = self.out.findText(settings["output_device"]) if i >= 0: self.out.setCurrentIndex(i) f.addRow("Playback device", self.out) # appearance self.bg_on = QCheckBox("Show the Arkham City wallpaper behind the filters") self.bg_on.setChecked(settings.get("background_enabled", True)) f.addRow("Background", self.bg_on) self.opacity = QSlider(Qt.Orientation.Horizontal) self.opacity.setRange(0, 100) self.opacity.setValue(int(round(float(settings.get("background_opacity", 0.1)) * 100))) f.addRow("Background strength", self.opacity) self.bg_on.toggled.connect(self.opacity.setEnabled) self.opacity.setEnabled(self.bg_on.isChecked()) bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel) bb.accepted.connect(self.accept) bb.rejected.connect(self.reject) f.addRow(bb) self.resize(640, 0) def _browse_game(self): d = pick_game_dir(self, self.game.text()) if d: self.game.setText(d) def _browse(self, edit): d = QFileDialog.getExistingDirectory(self, "Choose folder", edit.text()) if d: edit.setText(d) def values(self): s = dict(self.settings) gd = self.game.text().strip() s["game_dir"] = gd if is_game_dir(gd) else s.get("game_dir") s["store_root"] = self.store.text().strip() or None s["full_backup"] = self.full.isChecked() s["output_device"] = self.out.currentText() if self.out.currentIndex() > 0 else None s["background_enabled"] = self.bg_on.isChecked() s["background_opacity"] = self.opacity.value() / 100.0 return s class ExportModDialog(QDialog): """Pick which modified sounds go into a .acmod package.""" def __init__(self, items, parent=None): """items: [(key, name)]""" super().__init__(parent) self.setWindowTitle("Export mod package") lay = QVBoxLayout(self) f = QFormLayout() self.title = QLineEdit() self.author = QLineEdit() self.desc = QPlainTextEdit() self.desc.setMaximumHeight(70) f.addRow("Title", self.title) f.addRow("Author", self.author) f.addRow("Description", self.desc) lay.addLayout(f) lay.addWidget(QLabel("Sounds to include:")) self.list = QListWidget() for key, name in items: it = QListWidgetItem(name) it.setData(Qt.ItemDataRole.UserRole, key) it.setFlags(it.flags() | Qt.ItemFlag.ItemIsUserCheckable) it.setCheckState(Qt.CheckState.Checked) self.list.addItem(it) lay.addWidget(self.list) bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Save | QDialogButtonBox.StandardButton.Cancel) bb.accepted.connect(self.accept) bb.rejected.connect(self.reject) lay.addWidget(bb) self.resize(520, 480) def selected_keys(self): return [self.list.item(i).data(Qt.ItemDataRole.UserRole) for i in range(self.list.count()) if self.list.item(i).checkState() == Qt.CheckState.Checked] class ImportModDialog(QDialog): """Shows how each item of a package matches this game before installing.""" def __init__(self, manifest, items, parent=None, texts=()): super().__init__(parent) self.setWindowTitle("Import mod package") lay = QVBoxLayout(self) head = "%s" % (manifest.get("name") or "Mod package") if manifest.get("author"): head += " by %s" % manifest["author"] lab = QLabel(head + ("
" + manifest["description"] if manifest.get("description") else "")) lab.setWordWrap(True) lay.addWidget(lab) self.tree = QTreeWidget() self.tree.setHeaderLabels(["Sound", "Match", "Note"]) n_ok = 0 for im in items: note = {"exact": "same sound", "same line": "the same line in this language - will be re-encoded", "by name": "matched by name - will be re-encoded", "missing": im.note}[im.how] QTreeWidgetItem(self.tree, [im.item.get("name") or im.item["target"], im.how, note]) n_ok += im.how != "missing" t_ok = 0 for key, _text, problem in texts: QTreeWidgetItem(self.tree, [key, "text", problem or "will be applied"]) t_ok += not problem self.tree.resizeColumnToContents(0) lay.addWidget(self.tree) lay.addWidget(QLabel("%d of %d sounds and %d of %d texts will be installed." % (n_ok, len(items), t_ok, len(texts)))) n_ok += t_ok bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel) bb.button(QDialogButtonBox.StandardButton.Ok).setText("Install") bb.button(QDialogButtonBox.StandardButton.Ok).setEnabled(n_ok > 0) bb.accepted.connect(self.accept) bb.rejected.connect(self.reject) lay.addWidget(bb) self.resize(720, 420)