Files
UnheardCity/app/dialogs.py

170 lines
7.3 KiB
Python

"""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 = "<b>%s</b>" % (manifest.get("name") or "Mod package")
if manifest.get("author"):
head += " by %s" % manifest["author"]
lab = QLabel(head + ("<br>" + 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", "by name": "matched by name (other language) - 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)