Version 3.2.3: editor layout with aligned controls and full-width button bars; docs on recordings shared between languages
This commit is contained in:
104
app/editor.py
104
app/editor.py
@@ -54,6 +54,15 @@ class EditorPanel(QWidget):
|
||||
b.setToolTip(tip)
|
||||
return b
|
||||
|
||||
@staticmethod
|
||||
def _fill(*items):
|
||||
"""A row of buttons filling the column's width: [(widget, share)], equal edges in every column."""
|
||||
row = QHBoxLayout()
|
||||
row.setSpacing(6)
|
||||
for w, share in items:
|
||||
row.addWidget(w, share)
|
||||
return row
|
||||
|
||||
def _wave(self, kind, selectable=False):
|
||||
w = Waveform(selectable=selectable)
|
||||
w.accent, w.bg = theme.WAVE[kind], theme.WAVE_BG
|
||||
@@ -63,7 +72,7 @@ class EditorPanel(QWidget):
|
||||
"""Header on top; below, side by side (resizable): LISTEN | REPLACE (with the apply buttons) | SUBTITLE."""
|
||||
self.setObjectName("EditorPanel")
|
||||
lay = QVBoxLayout(self)
|
||||
lay.setContentsMargins(10, 6, 10, 6)
|
||||
lay.setContentsMargins(8, 6, 8, 6)
|
||||
lay.setSpacing(4)
|
||||
self.title = QLabel()
|
||||
self.title.setObjectName("EditorTitle")
|
||||
@@ -87,41 +96,47 @@ class EditorPanel(QWidget):
|
||||
# listen ----------------------------------------------------------------------------------
|
||||
g = QGroupBox("LISTEN")
|
||||
gl = QGridLayout(g)
|
||||
gl.setContentsMargins(8, 8, 8, 8)
|
||||
gl.setVerticalSpacing(4)
|
||||
self.wf_orig = self._wave("orig")
|
||||
self.wf_inst = self._wave("inst")
|
||||
self.b_play_orig = self._btn("Original", "play", lambda: self.play("orig"), "Play the original (Space)", icons.STEEL)
|
||||
self.b_play_inst = self._btn("Installed", "play", lambda: self.play("inst"), "Play what the game has now", "#8f4a4e")
|
||||
self.b_stop = self._btn("Stop", "stop", self.stop, "Stop (Esc)")
|
||||
self.b_ab = self._btn("A/B", "ab", self.ab_switch, "Switch between original and replacement at the same position",
|
||||
icons.ACCENT)
|
||||
self.cb_loop = self._btn("Loop", "loop", None, "Repeat playback")
|
||||
# playback options next to the volume: icons with tooltips
|
||||
self.b_ab = self._btn("", "ab", self.ab_switch, "A/B: switch between the original and the replacement at "
|
||||
"the same position", icons.ACCENT)
|
||||
self.cb_loop = self._btn("", "loop", None, "Loop: repeat playback")
|
||||
self.cb_loop.setCheckable(True)
|
||||
for b in (self.b_ab, self.cb_loop):
|
||||
b.setFixedWidth(b.sizeHint().height() + 4)
|
||||
self.sl_vol = QSlider(Qt.Orientation.Horizontal)
|
||||
self.sl_vol.setRange(0, 100)
|
||||
self.sl_vol.setValue(80)
|
||||
self.sl_vol.setMaximumWidth(120)
|
||||
self.sl_vol.setMinimumWidth(80)
|
||||
self.sl_vol.setToolTip("Volume")
|
||||
self.sl_vol.valueChanged.connect(lambda v: self.ctx.player.set_volume(v / 100.0))
|
||||
self.lbl_time = QLabel("0:00.00")
|
||||
self.lbl_time.setObjectName("Muted")
|
||||
self.lbl_time.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
|
||||
self.lbl_time.setMinimumWidth(self.lbl_time.fontMetrics().horizontalAdvance("00:00.00") + 4)
|
||||
gl.addWidget(QLabel("Original"), 0, 0)
|
||||
gl.addWidget(self.wf_orig, 0, 1)
|
||||
self.lbl_inst = QLabel("Installed")
|
||||
gl.addWidget(self.lbl_inst, 1, 0)
|
||||
gl.addWidget(self.wf_inst, 1, 1)
|
||||
row = QHBoxLayout()
|
||||
for w in (self.b_play_orig, self.b_play_inst, self.b_stop):
|
||||
row.addWidget(w)
|
||||
row.addStretch(1)
|
||||
gl.addLayout(row, 2, 0, 1, 2)
|
||||
row = QHBoxLayout()
|
||||
for w in (self.b_ab, self.cb_loop, QLabel("Volume"), self.sl_vol, self.lbl_time):
|
||||
row.addWidget(w)
|
||||
row.addStretch(1)
|
||||
gl.addLayout(row, 3, 0, 1, 2)
|
||||
gl.setRowStretch(2, 1) # controls at the bottom, level with the other columns' actions
|
||||
gl.addLayout(self._fill((self.b_play_orig, 1), (self.b_play_inst, 1), (self.b_stop, 1)), 4, 0, 1, 2)
|
||||
vol = QHBoxLayout() # playback options: A/B, loop, volume, position
|
||||
vol.setSpacing(6)
|
||||
vol.addWidget(self.b_ab)
|
||||
vol.addWidget(self.cb_loop)
|
||||
vol.addSpacing(6)
|
||||
vol.addWidget(QLabel("Volume"))
|
||||
vol.addWidget(self.sl_vol, 1)
|
||||
vol.addWidget(self.lbl_time)
|
||||
gl.addLayout(vol, 3, 0, 1, 2)
|
||||
gl.setColumnStretch(1, 1)
|
||||
gl.setRowStretch(4, 1)
|
||||
self.wf_orig.seekRequested.connect(lambda t: self.play("orig", t))
|
||||
self.wf_inst.seekRequested.connect(lambda t: self.play("inst", t))
|
||||
listen = g
|
||||
@@ -129,25 +144,29 @@ class EditorPanel(QWidget):
|
||||
# replace ---------------------------------------------------------------------------------
|
||||
g = QGroupBox("REPLACE")
|
||||
rl = QVBoxLayout(g)
|
||||
rl.setSpacing(4)
|
||||
rl.setContentsMargins(8, 8, 8, 8)
|
||||
rl.setSpacing(6)
|
||||
src = QHBoxLayout()
|
||||
src.setSpacing(6)
|
||||
self.b_load = self._btn("Load file…", "open", self.load_file_dialog,
|
||||
"WAV, FLAC, OGG, OPUS, MP3, AIFF (M4A/WMA need ffmpeg installed) (Ctrl+O)")
|
||||
self.cmb_in = QComboBox()
|
||||
self.cmb_in.setMinimumWidth(110)
|
||||
self.cmb_in.setMaximumWidth(200)
|
||||
self.cmb_in.setToolTip("Recording device")
|
||||
self.b_rec = self._btn("Record", "record", self.toggle_record, "Record from the selected input")
|
||||
self.meter = QProgressBar()
|
||||
self.meter.setRange(0, 100)
|
||||
self.meter.setTextVisible(False)
|
||||
self.meter.setMaximumWidth(60)
|
||||
self.meter.setFixedWidth(60)
|
||||
self.meter.setToolTip("Input level")
|
||||
self.lbl_rec = QLabel("")
|
||||
self.lbl_rec.setObjectName("Muted")
|
||||
for w in (self.b_load, self.cmb_in, self.b_rec, self.meter, self.lbl_rec):
|
||||
src.addWidget(self.b_load, 1) # the row fills the column: file | microphone, level
|
||||
src.addSpacing(10)
|
||||
src.addWidget(self.cmb_in, 2)
|
||||
src.addWidget(self.b_rec, 1)
|
||||
for w in (self.meter, self.lbl_rec):
|
||||
src.addWidget(w)
|
||||
src.addStretch(1)
|
||||
rl.addLayout(src)
|
||||
self.wf_new = self._wave("new", selectable=True)
|
||||
self.wf_new.setToolTip("Click: play from here. Right-drag (or Shift+drag): select the part to keep.")
|
||||
@@ -160,12 +179,12 @@ class EditorPanel(QWidget):
|
||||
s_.setDecimals(2)
|
||||
s_.setSuffix(" s")
|
||||
s_.setRange(0, 36000)
|
||||
s_.setMaximumWidth(92)
|
||||
s_.setMaximumWidth(80)
|
||||
s_.valueChanged.connect(self._changed)
|
||||
self.sp_gain = QDoubleSpinBox()
|
||||
self.sp_gain.setRange(-40, 40)
|
||||
self.sp_gain.setSuffix(" dB")
|
||||
self.sp_gain.setMaximumWidth(92)
|
||||
self.sp_gain.setMaximumWidth(80)
|
||||
self.sp_gain.valueChanged.connect(self._changed)
|
||||
self.cb_norm = QCheckBox("Normalize")
|
||||
self.cb_match = QCheckBox("Match loudness")
|
||||
@@ -175,15 +194,24 @@ class EditorPanel(QWidget):
|
||||
self.b_silence = self._btn("Trim silence", None, self.trim_silence, "Cut silence at the start and end")
|
||||
self.b_play_new = self._btn("Replacement", "play", lambda: self.play("new"), "Play the processed replacement",
|
||||
icons.ACCENT)
|
||||
proc = QHBoxLayout()
|
||||
for w in (QLabel("Keep"), self.sp_a, QLabel("to"), self.sp_b, self.b_silence, self.b_play_new):
|
||||
proc.addWidget(w)
|
||||
proc.addStretch(1)
|
||||
rl.addLayout(proc)
|
||||
proc = QHBoxLayout()
|
||||
for w in (QLabel("Gain"), self.sp_gain, self.cb_norm, self.cb_match):
|
||||
proc.addWidget(w)
|
||||
proc.addStretch(1)
|
||||
# edit controls in a grid: labels in one column, the fields aligned, playback on the right
|
||||
proc = QGridLayout()
|
||||
proc.setHorizontalSpacing(6)
|
||||
proc.setVerticalSpacing(6)
|
||||
proc.addWidget(QLabel("Keep"), 0, 0)
|
||||
proc.addWidget(self.sp_a, 0, 1)
|
||||
proc.addWidget(QLabel("to"), 0, 2, Qt.AlignmentFlag.AlignCenter)
|
||||
proc.addWidget(self.sp_b, 0, 3)
|
||||
proc.addLayout(self._fill((self.b_silence, 1), (self.b_play_new, 1)), 0, 4) # the rest of the width
|
||||
proc.addWidget(QLabel("Gain"), 1, 0)
|
||||
proc.addWidget(self.sp_gain, 1, 1)
|
||||
checks = QHBoxLayout()
|
||||
checks.setSpacing(12)
|
||||
checks.addWidget(self.cb_norm)
|
||||
checks.addWidget(self.cb_match)
|
||||
checks.addStretch(1)
|
||||
proc.addLayout(checks, 1, 2, 1, 3)
|
||||
proc.setColumnStretch(4, 1)
|
||||
rl.addLayout(proc)
|
||||
info = QHBoxLayout()
|
||||
self.lbl_new = QLabel()
|
||||
@@ -200,7 +228,6 @@ class EditorPanel(QWidget):
|
||||
info.addWidget(self.lbl_warn, 1)
|
||||
rl.addLayout(info)
|
||||
# apply (belongs to the replacement) ------------------------------------------------------
|
||||
act = QHBoxLayout()
|
||||
self.b_apply = self._btn("Apply to game", "apply", self.apply,
|
||||
"Encode the replacement and install it in the game files (Ctrl+S)", "#ffffff")
|
||||
self.b_apply.setObjectName("Primary")
|
||||
@@ -209,9 +236,9 @@ class EditorPanel(QWidget):
|
||||
"Save the original (or the replacement) as a WAV file")
|
||||
self.busy = Spinner(16)
|
||||
self.lbl_busy = QLabel()
|
||||
for w in (self.b_apply, self.b_revert, self.b_export, self.busy, self.lbl_busy):
|
||||
act.addWidget(w)
|
||||
act.addStretch(1)
|
||||
info.addWidget(self.busy, 0, Qt.AlignmentFlag.AlignTop) # encoding/writing: next to the status
|
||||
info.addWidget(self.lbl_busy, 0, Qt.AlignmentFlag.AlignTop)
|
||||
act = self._fill((self.b_apply, 2), (self.b_revert, 1), (self.b_export, 1))
|
||||
rl.addStretch(1)
|
||||
rl.addLayout(act)
|
||||
replace = g
|
||||
@@ -220,12 +247,13 @@ class EditorPanel(QWidget):
|
||||
from .textpanel import SubtitleBox
|
||||
gs = QGroupBox("SUBTITLE")
|
||||
gsl = QVBoxLayout(gs)
|
||||
gsl.setContentsMargins(8, 8, 8, 8)
|
||||
self.subtitle = SubtitleBox(self.ctx, gs)
|
||||
gsl.addWidget(self.subtitle)
|
||||
gsl.addStretch(1)
|
||||
gsl.addWidget(self.subtitle, 1)
|
||||
|
||||
self.columns = QSplitter(Qt.Orientation.Horizontal)
|
||||
self.columns.setChildrenCollapsible(False)
|
||||
self.columns.setHandleWidth(4)
|
||||
for w, stretch in ((listen, 3), (replace, 4), (gs, 3)):
|
||||
self.columns.addWidget(w)
|
||||
self.columns.setStretchFactor(self.columns.count() - 1, stretch)
|
||||
|
||||
@@ -143,12 +143,16 @@ class SubtitleBox(QWidget):
|
||||
super().__init__(parent)
|
||||
self.win = win
|
||||
self.keys = []
|
||||
self.lay = QFormLayout(self)
|
||||
outer = QVBoxLayout(self)
|
||||
outer.setContentsMargins(0, 0, 0, 0)
|
||||
outer.setSpacing(6)
|
||||
self.lay = QFormLayout() # one line edit per subtitle line
|
||||
self.lay.setContentsMargins(0, 0, 0, 0)
|
||||
self.edits = []
|
||||
self.lbl = QLabel()
|
||||
self.lbl.setObjectName("Muted")
|
||||
row = QHBoxLayout()
|
||||
row.setSpacing(6)
|
||||
self.b_apply = QPushButton(icons.icon("apply"), "Apply")
|
||||
self.b_revert = QPushButton(icons.icon("revert"), "Revert")
|
||||
self.b_open = QPushButton(icons.icon("search"), "Show in list")
|
||||
@@ -158,16 +162,14 @@ class SubtitleBox(QWidget):
|
||||
self.b_apply.clicked.connect(self.apply)
|
||||
self.b_revert.clicked.connect(self.revert)
|
||||
self.b_open.clicked.connect(lambda: self.keys and self.open_in_list.emit(self.keys[0]))
|
||||
for b in (self.b_apply, self.b_revert):
|
||||
row.addWidget(b)
|
||||
row.addStretch(1)
|
||||
row2 = QHBoxLayout()
|
||||
row2.addWidget(self.b_open)
|
||||
row2.addStretch(1)
|
||||
for b in (self.b_apply, self.b_revert): # two halves of the column, like the sound's action bar
|
||||
row.addWidget(b, 1)
|
||||
self.lbl.setWordWrap(True)
|
||||
self.lay.addRow(self.lbl)
|
||||
self.lay.addRow(row)
|
||||
self.lay.addRow(row2)
|
||||
outer.addLayout(self.lay)
|
||||
outer.addWidget(self.lbl) # room counter
|
||||
outer.addWidget(self.b_open) # the way to the Subtitles tab, across the column
|
||||
outer.addStretch(1) # actions at the bottom, level with the sound's
|
||||
outer.addLayout(row)
|
||||
|
||||
def set_sound(self, s):
|
||||
for e in self.edits:
|
||||
|
||||
Reference in New Issue
Block a user