Version 3.4.0: Edit tab (edit list, large subtitle, replacements with effects and presets), Sounds tab only browses, ackit fx (pedalboard + own reverb) and replace --fx, first Linux build, catalog without a mod package
This commit is contained in:
@@ -1,12 +1,13 @@
|
||||
"""Main window. Tabs: Sounds (filters + sound table on top, single-sound editor below, with the sound's
|
||||
subtitle), Subtitles and Menu text (app/textpanel.py)."""
|
||||
"""Main window. Tabs: Sounds (browse: filters + sound table, a preview below; app/preview.py), Edit (the edit list,
|
||||
the editor and the effects: app/edit.py; the only place where sounds are replaced), Subtitles and Menu text
|
||||
(app/textpanel.py)."""
|
||||
import os
|
||||
|
||||
from PySide6.QtCore import QByteArray, QEvent, QItemSelectionModel, QSize, Qt, QTimer
|
||||
from PySide6.QtCore import QByteArray, QItemSelectionModel, QSize, Qt, QTimer
|
||||
from PySide6.QtGui import QFont, QKeySequence, QShortcut
|
||||
from PySide6.QtWidgets import (QAbstractItemView, QCheckBox, QComboBox, QDialog, QFileDialog, QFormLayout,
|
||||
QHBoxLayout, QHeaderView, QLabel, QLineEdit, QMainWindow, QMenu, QMessageBox,
|
||||
QPushButton, QScrollArea, QSplitter, QTableView, QTabWidget, QToolBar, QVBoxLayout,
|
||||
QPushButton, QSplitter, QTableView, QTabWidget, QToolBar, QVBoxLayout,
|
||||
QWidget)
|
||||
|
||||
from ackit import settings as settings_mod, workspace as W
|
||||
@@ -17,15 +18,15 @@ from ackit.texts import TextConflictError
|
||||
from . import icons, theme
|
||||
from .audioio import Player, find_device, output_devices
|
||||
from .dialogs import ExportModDialog, ImportModDialog, SettingsDialog, pick_game_dir
|
||||
from .editor import EditorPanel
|
||||
from .edit import EditPanel
|
||||
from .models import C_FAV, C_NAME, COLS, SoundFilter, SoundModel
|
||||
from .preview import PreviewPanel
|
||||
from .spinner import BusyOverlay, Spinner
|
||||
from .textpanel import TextPanel
|
||||
from .userdata import UserData
|
||||
from .workers import Runner
|
||||
|
||||
SIDEBAR_MIN, SIDEBAR_MAX = 220, 320
|
||||
TABLE_MIN = 160 # the sound list never gets smaller than this when fitting the editor
|
||||
|
||||
ANY = "Any"
|
||||
|
||||
@@ -196,20 +197,16 @@ class MainWindow(QMainWindow):
|
||||
self.table.sortByColumn(C_NAME, Qt.SortOrder.AscendingOrder)
|
||||
self.table.selectionModel().currentRowChanged.connect(self._current_changed)
|
||||
self.table.clicked.connect(self._clicked)
|
||||
self.table.doubleClicked.connect(lambda _: self.editor.play("orig"))
|
||||
self.table.doubleClicked.connect(lambda _: self.preview.play("orig"))
|
||||
self.table.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
||||
self.table.customContextMenuRequested.connect(self._context_menu)
|
||||
|
||||
# editor
|
||||
self.editor = EditorPanel(self)
|
||||
self.editor.applied.connect(self._after_change)
|
||||
self.editor.reverted.connect(self._after_change)
|
||||
self.editor.message.connect(lambda m: self.statusBar().showMessage(m, 8000))
|
||||
scroll = QScrollArea()
|
||||
scroll.setWidget(self.editor)
|
||||
scroll.setWidgetResizable(True)
|
||||
# preview of the selected sound (no editing here)
|
||||
self.preview = PreviewPanel(self)
|
||||
self.preview.edit_requested.connect(self.edit_selected)
|
||||
self.preview.add_requested.connect(self.add_selected_to_edit)
|
||||
|
||||
# sidebar | sound list on top, the editor below across the whole width
|
||||
# sidebar | sound list on top, the preview below across the whole width
|
||||
hs = QSplitter(Qt.Orientation.Horizontal)
|
||||
hs.addWidget(side)
|
||||
hs.addWidget(self.table)
|
||||
@@ -217,18 +214,19 @@ class MainWindow(QMainWindow):
|
||||
hs.setSizes([SIDEBAR_MAX, 100000])
|
||||
self.vsplit = QSplitter(Qt.Orientation.Vertical)
|
||||
self.vsplit.addWidget(hs)
|
||||
self.vsplit.addWidget(scroll)
|
||||
self.vsplit.setStretchFactor(0, 1) # extra height goes to the list; the editor keeps its fitted height
|
||||
self.vsplit.addWidget(self.preview)
|
||||
self.vsplit.setStretchFactor(0, 1) # extra height goes to the list
|
||||
self.vsplit.setStretchFactor(1, 0)
|
||||
self.scroll = scroll
|
||||
self._fitted = False
|
||||
self._user_sized = False # once the divider is dragged, keep the user's choice
|
||||
self.vsplit.splitterMoved.connect(lambda *_: setattr(self, "_user_sized", True))
|
||||
self.editor.installEventFilter(self)
|
||||
# sections: sounds, subtitles, menu text
|
||||
self.vsplit.setChildrenCollapsible(False)
|
||||
# sections: sounds (browse), edit, subtitles, menu text
|
||||
self.tabs = QTabWidget()
|
||||
self.tabs.setDocumentMode(True)
|
||||
self.tabs.addTab(self.vsplit, icons.icon("wave"), "Sounds")
|
||||
self.edit = EditPanel(self)
|
||||
self.editor = self.edit.editor # the one sound editor
|
||||
self.tabs.addTab(self.edit, icons.icon("edit"), "Edit")
|
||||
self.edit.message.connect(lambda m: self.statusBar().showMessage(m, 8000))
|
||||
self.editor.message.connect(lambda m: self.statusBar().showMessage(m, 8000))
|
||||
self.subs = TextPanel(self, "subs")
|
||||
self.menu = TextPanel(self, "menu")
|
||||
self.tabs.addTab(self.subs, icons.icon("ab"), "Subtitles")
|
||||
@@ -255,12 +253,15 @@ class MainWindow(QMainWindow):
|
||||
|
||||
sc = Qt.ShortcutContext.WindowShortcut
|
||||
QShortcut(QKeySequence("Ctrl+F"), self, self.search.setFocus, context=sc)
|
||||
QShortcut(QKeySequence("Ctrl+S"), self, lambda: self.editor.apply(), context=sc)
|
||||
QShortcut(QKeySequence("Ctrl+O"), self, lambda: self.editor.load_file_dialog(), context=sc)
|
||||
QShortcut(QKeySequence("Escape"), self, self.editor.stop, context=sc)
|
||||
QShortcut(QKeySequence(Qt.Key.Key_Space), self.table, self.editor.toggle_play,
|
||||
context=Qt.ShortcutContext.WidgetShortcut)
|
||||
QShortcut(QKeySequence(Qt.Key.Key_F2), self.table, self.edit_notes, context=Qt.ShortcutContext.WidgetShortcut)
|
||||
edit = Qt.ShortcutContext.WidgetWithChildrenShortcut # editing shortcuts: in the Edit tab only
|
||||
QShortcut(QKeySequence("Ctrl+S"), self.edit, lambda: self.editor.apply(), context=edit)
|
||||
QShortcut(QKeySequence("Ctrl+O"), self.edit, lambda: self.editor.load_file_dialog(), context=edit)
|
||||
QShortcut(QKeySequence("Escape"), self, self.player.stop, context=sc)
|
||||
wsc = Qt.ShortcutContext.WidgetShortcut
|
||||
QShortcut(QKeySequence(Qt.Key.Key_Space), self.table, self.preview.toggle_play, context=wsc)
|
||||
QShortcut(QKeySequence(Qt.Key.Key_Return), self.table, self.edit_selected, context=wsc)
|
||||
QShortcut(QKeySequence(Qt.Key.Key_Enter), self.table, self.edit_selected, context=wsc)
|
||||
QShortcut(QKeySequence(Qt.Key.Key_F2), self.table, self.edit_notes, context=wsc)
|
||||
|
||||
# --- dialogs (overridable in tests) -----------------------------------------------------------
|
||||
def confirm(self, title, text):
|
||||
@@ -355,6 +356,7 @@ class MainWindow(QMainWindow):
|
||||
fill(self.cmb_arch, list(ws.catalog.archives))
|
||||
fill(self.cmb_tag, self.userdata.all_tags())
|
||||
self._filters_changed()
|
||||
self.edit.populate(ws)
|
||||
|
||||
def _fill_categories(self):
|
||||
g = self.cmb_group.currentText()
|
||||
@@ -405,7 +407,22 @@ class MainWindow(QMainWindow):
|
||||
|
||||
def _current_changed(self, cur, prev):
|
||||
if cur.isValid():
|
||||
self.editor.set_sound(self.proxy.sound_at(cur))
|
||||
self.preview.set_sound(self.proxy.sound_at(cur))
|
||||
|
||||
def edit_selected(self):
|
||||
"""Selected sounds onto the edit list; the Edit tab opens on the first."""
|
||||
sel = self.selected_sounds() or ([self.preview.sound] if self.preview.sound else [])
|
||||
if sel:
|
||||
self.edit_sounds(sel)
|
||||
|
||||
def edit_sounds(self, sounds):
|
||||
self.edit.add(sounds, open_first=True)
|
||||
self.tabs.setCurrentWidget(self.edit)
|
||||
|
||||
def add_selected_to_edit(self):
|
||||
sel = self.selected_sounds()
|
||||
self.edit.add(sel)
|
||||
self.statusBar().showMessage("%d sound(s) on the edit list (Edit tab)" % len(sel), 5000)
|
||||
|
||||
def _clicked(self, index):
|
||||
if index.column() == C_FAV:
|
||||
@@ -429,7 +446,10 @@ class MainWindow(QMainWindow):
|
||||
if not sel:
|
||||
return
|
||||
m = QMenu(self)
|
||||
m.addAction("Play original", lambda: self.editor.play("orig"))
|
||||
m.addAction(icons.icon("edit"), "Edit (Enter)", self.edit_selected)
|
||||
m.addAction(icons.icon("add"), "Add to edit list", self.add_selected_to_edit)
|
||||
m.addSeparator()
|
||||
m.addAction("Play original (Space)", lambda: self.preview.play("orig"))
|
||||
m.addAction("Notes && tags… (F2)", self.edit_notes)
|
||||
m.addAction("Toggle favorite", lambda: self._toggle_fav(sel))
|
||||
m.addSeparator()
|
||||
@@ -492,6 +512,8 @@ class MainWindow(QMainWindow):
|
||||
if panel.kind in self._text_loaded:
|
||||
panel.refresh()
|
||||
self.editor.subtitle.set_sound(self.editor.sound)
|
||||
if self.preview.sound:
|
||||
self.preview.set_sound(self.preview.sound)
|
||||
|
||||
def show_sound(self, key):
|
||||
"""Sounds tab with `key` selected (from a subtitle)."""
|
||||
@@ -515,8 +537,12 @@ class MainWindow(QMainWindow):
|
||||
|
||||
# --- changes ----------------------------------------------------------------------------------
|
||||
def _after_change(self, key=None):
|
||||
self.model.refresh_keys(modified=self.ws.modified_keys()) # the view refilters/re-sorts itself
|
||||
modified = self.ws.modified_keys()
|
||||
self.model.refresh_keys(modified=modified) # the view refilters/re-sorts itself
|
||||
self._update_count()
|
||||
self.edit.refresh(modified)
|
||||
if self.preview.sound:
|
||||
self.preview.set_sound(self.preview.sound)
|
||||
|
||||
def revert_sounds(self, sounds):
|
||||
sounds = [s for s in sounds if self.ws.store.get(s.key)]
|
||||
@@ -572,7 +598,7 @@ class MainWindow(QMainWindow):
|
||||
self._after_change()
|
||||
self._texts_changed()
|
||||
if self.editor.sound:
|
||||
self.editor.set_sound(self.editor.sound)
|
||||
self.edit.load(self.editor.sound)
|
||||
|
||||
# --- changed underneath -----------------------------------------------------------------------
|
||||
def check_problems(self):
|
||||
@@ -744,31 +770,8 @@ class MainWindow(QMainWindow):
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def fit_editor(self):
|
||||
"""Give the editor panel the height of its contents (everything visible), the list gets the rest."""
|
||||
total = sum(self.vsplit.sizes()) or self.vsplit.height()
|
||||
lay = self.editor.layout()
|
||||
need = self.editor.sizeHint().height()
|
||||
if lay.hasHeightForWidth(): # wrapped labels: height at the actual width
|
||||
need = max(need, lay.heightForWidth(self.scroll.viewport().width()))
|
||||
need += 2 * self.scroll.frameWidth()
|
||||
if self.editor.minimumSizeHint().width() > self.scroll.viewport().width(): # narrow window: room for the h-bar
|
||||
need += self.scroll.horizontalScrollBar().sizeHint().height()
|
||||
editor_h = min(need, max(0, total - TABLE_MIN))
|
||||
self.vsplit.setSizes([total - editor_h, editor_h])
|
||||
|
||||
def eventFilter(self, obj, e):
|
||||
if obj is self.editor and e.type() == QEvent.Type.LayoutRequest and self._fitted and not self._user_sized:
|
||||
QTimer.singleShot(0, self.fit_editor)
|
||||
return super().eventFilter(obj, e)
|
||||
|
||||
def showEvent(self, e):
|
||||
super().showEvent(e)
|
||||
if not self._fitted:
|
||||
self._fitted = True
|
||||
QTimer.singleShot(0, self.fit_editor)
|
||||
|
||||
def closeEvent(self, e):
|
||||
self.player.stop()
|
||||
self.edit.flush()
|
||||
self.save_settings()
|
||||
super().closeEvent(e)
|
||||
|
||||
Reference in New Issue
Block a user