Merge branch 'dev'
This commit is contained in:
commit
ac18773ebd
@ -107,7 +107,11 @@ class AudacityController:
|
|||||||
f"and pipes exist at {self.pipe_to} and {self.pipe_from}."
|
f"and pipes exist at {self.pipe_to} and {self.pipe_from}."
|
||||||
)
|
)
|
||||||
|
|
||||||
# Send test command to Audacity
|
def _test_connectivity(self) -> None:
|
||||||
|
"""
|
||||||
|
Send test command to Audacity
|
||||||
|
"""
|
||||||
|
|
||||||
response = self._send_command(Config.AUDACITY_TEST_COMMAND)
|
response = self._send_command(Config.AUDACITY_TEST_COMMAND)
|
||||||
if response != Config.AUDACITY_TEST_RESPONSE:
|
if response != Config.AUDACITY_TEST_RESPONSE:
|
||||||
raise ApplicationError(
|
raise ApplicationError(
|
||||||
|
|||||||
@ -95,6 +95,8 @@ class Config(object):
|
|||||||
ROOT = os.environ.get("ROOT") or "/home/kae/music"
|
ROOT = os.environ.get("ROOT") or "/home/kae/music"
|
||||||
ROWS_FROM_ZERO = True
|
ROWS_FROM_ZERO = True
|
||||||
SCROLL_TOP_MARGIN = 3
|
SCROLL_TOP_MARGIN = 3
|
||||||
|
SECTION_ENDINGS = ("-", "+-", "-+")
|
||||||
|
SECTION_STARTS = ("+", "+-", "-+")
|
||||||
SONGFACTS_ON_NEXT = False
|
SONGFACTS_ON_NEXT = False
|
||||||
START_GAP_WARNING_THRESHOLD = 300
|
START_GAP_WARNING_THRESHOLD = 300
|
||||||
TEXT_NO_TRACK_NO_NOTE = "[Section header]"
|
TEXT_NO_TRACK_NO_NOTE = "[Section header]"
|
||||||
|
|||||||
@ -567,7 +567,7 @@ class PlaylistRows(dbtables.PlaylistRowsTable):
|
|||||||
session: Session,
|
session: Session,
|
||||||
playlist_id: int,
|
playlist_id: int,
|
||||||
sqla_map: List[dict[str, int]],
|
sqla_map: List[dict[str, int]],
|
||||||
dummy_for_profiling=None,
|
dummy_for_profiling: Optional[int] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Take a {plrid: row_number} dictionary and update the row numbers accordingly
|
Take a {plrid: row_number} dictionary and update the row numbers accordingly
|
||||||
|
|||||||
@ -1066,7 +1066,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
webbrowser.get("browser").open_new_tab(url)
|
webbrowser.get("browser").open_new_tab(url)
|
||||||
|
|
||||||
@line_profiler.profile
|
@line_profiler.profile
|
||||||
def paste_rows(self, dummy_for_profiling=None) -> None:
|
def paste_rows(self, dummy_for_profiling: Optional[int] = None) -> None:
|
||||||
"""
|
"""
|
||||||
Paste earlier cut rows.
|
Paste earlier cut rows.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -377,7 +377,7 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
else:
|
else:
|
||||||
return QVariant(self.header_text(rat))
|
return QVariant(self.header_text(rat))
|
||||||
else:
|
else:
|
||||||
return QVariant()
|
return QVariant("")
|
||||||
|
|
||||||
if column == Col.START_TIME.value:
|
if column == Col.START_TIME.value:
|
||||||
start_time = rat.forecast_start_time
|
start_time = rat.forecast_start_time
|
||||||
@ -395,7 +395,7 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
if rat.intro:
|
if rat.intro:
|
||||||
return QVariant(f"{rat.intro / 1000:{Config.INTRO_SECONDS_FORMAT}}")
|
return QVariant(f"{rat.intro / 1000:{Config.INTRO_SECONDS_FORMAT}}")
|
||||||
else:
|
else:
|
||||||
return QVariant()
|
return QVariant("")
|
||||||
|
|
||||||
dispatch_table = {
|
dispatch_table = {
|
||||||
Col.ARTIST.value: QVariant(rat.artist),
|
Col.ARTIST.value: QVariant(rat.artist),
|
||||||
@ -618,7 +618,7 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
Process possible section timing directives embeded in header
|
Process possible section timing directives embeded in header
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if rat.note.endswith("+"):
|
if rat.note.endswith(Config.SECTION_STARTS):
|
||||||
return self.start_of_timed_section_header(rat)
|
return self.start_of_timed_section_header(rat)
|
||||||
|
|
||||||
elif rat.note.endswith("="):
|
elif rat.note.endswith("="):
|
||||||
@ -748,7 +748,10 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
|
|
||||||
@line_profiler.profile
|
@line_profiler.profile
|
||||||
def move_rows(
|
def move_rows(
|
||||||
self, from_rows: list[int], to_row_number: int, dummy_for_profiling=None
|
self,
|
||||||
|
from_rows: list[int],
|
||||||
|
to_row_number: int,
|
||||||
|
dummy_for_profiling: Optional[int] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Move the playlist rows given to to_row and below.
|
Move the playlist rows given to to_row and below.
|
||||||
@ -820,7 +823,7 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
from_rows: list[int],
|
from_rows: list[int],
|
||||||
to_row_number: int,
|
to_row_number: int,
|
||||||
to_playlist_id: int,
|
to_playlist_id: int,
|
||||||
dummy_for_profiling=None,
|
dummy_for_profiling: Optional[int] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Move the playlist rows given to to_row and below of to_playlist.
|
Move the playlist rows given to to_row and below of to_playlist.
|
||||||
@ -1005,7 +1008,6 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
refresh_row().
|
refresh_row().
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
# Note where each playlist_id is
|
# Note where each playlist_id is
|
||||||
plid_to_row: dict[int, int] = {}
|
plid_to_row: dict[int, int] = {}
|
||||||
for oldrow in self.playlist_rows:
|
for oldrow in self.playlist_rows:
|
||||||
@ -1024,7 +1026,12 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
# Copy to self.playlist_rows
|
# Copy to self.playlist_rows
|
||||||
self.playlist_rows = new_playlist_rows
|
self.playlist_rows = new_playlist_rows
|
||||||
|
|
||||||
def load_data(self, session: db.session, dummy_for_profiling=None) -> None:
|
# Same as refresh data, but only used when creating playslit.
|
||||||
|
# Distinguishes profile time between initial load and other
|
||||||
|
# refreshes.
|
||||||
|
def load_data(
|
||||||
|
self, session: db.session, dummy_for_profiling: Optional[int] = None
|
||||||
|
) -> None:
|
||||||
"""Populate self.playlist_rows with playlist data"""
|
"""Populate self.playlist_rows with playlist data"""
|
||||||
|
|
||||||
# Same as refresh data, but only used when creating playslit.
|
# Same as refresh data, but only used when creating playslit.
|
||||||
@ -1465,7 +1472,7 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
for row_number in range(rat.row_number + 1, len(self.playlist_rows)):
|
for row_number in range(rat.row_number + 1, len(self.playlist_rows)):
|
||||||
row_rat = self.playlist_rows[row_number]
|
row_rat = self.playlist_rows[row_number]
|
||||||
if self.is_header_row(row_number):
|
if self.is_header_row(row_number):
|
||||||
if row_rat.note.endswith("-"):
|
if row_rat.note.endswith(Config.SECTION_ENDINGS):
|
||||||
return (
|
return (
|
||||||
f"{rat.note[:-1].strip()} "
|
f"{rat.note[:-1].strip()} "
|
||||||
f"[{count} tracks, {ms_to_mmss(duration)} unplayed]"
|
f"[{count} tracks, {ms_to_mmss(duration)} unplayed]"
|
||||||
|
|||||||
168
app/playlists.py
168
app/playlists.py
@ -1,5 +1,5 @@
|
|||||||
# Standard library imports
|
# Standard library imports
|
||||||
from typing import Callable, cast, List, Optional, TYPE_CHECKING
|
from typing import Any, Callable, cast, List, Optional, TYPE_CHECKING
|
||||||
|
|
||||||
# PyQt imports
|
# PyQt imports
|
||||||
from PyQt6.QtCore import (
|
from PyQt6.QtCore import (
|
||||||
@ -8,19 +8,19 @@ from PyQt6.QtCore import (
|
|||||||
QModelIndex,
|
QModelIndex,
|
||||||
QObject,
|
QObject,
|
||||||
QItemSelection,
|
QItemSelection,
|
||||||
|
QSize,
|
||||||
Qt,
|
Qt,
|
||||||
QTimer,
|
QTimer,
|
||||||
)
|
)
|
||||||
from PyQt6.QtGui import QAction, QKeyEvent
|
from PyQt6.QtGui import QAction, QDropEvent, QKeyEvent, QTextDocument
|
||||||
from PyQt6.QtWidgets import (
|
from PyQt6.QtWidgets import (
|
||||||
QAbstractItemDelegate,
|
QAbstractItemDelegate,
|
||||||
QAbstractItemView,
|
QAbstractItemView,
|
||||||
QApplication,
|
QApplication,
|
||||||
QDoubleSpinBox,
|
QDoubleSpinBox,
|
||||||
QHeaderView,
|
QFrame,
|
||||||
QMenu,
|
QMenu,
|
||||||
QMessageBox,
|
QMessageBox,
|
||||||
QPlainTextEdit,
|
|
||||||
QProxyStyle,
|
QProxyStyle,
|
||||||
QStyle,
|
QStyle,
|
||||||
QStyledItemDelegate,
|
QStyledItemDelegate,
|
||||||
@ -28,10 +28,12 @@ from PyQt6.QtWidgets import (
|
|||||||
QStyleOptionViewItem,
|
QStyleOptionViewItem,
|
||||||
QTableView,
|
QTableView,
|
||||||
QTableWidgetItem,
|
QTableWidgetItem,
|
||||||
|
QTextEdit,
|
||||||
QWidget,
|
QWidget,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Third party imports
|
# Third party imports
|
||||||
|
import line_profiler
|
||||||
|
|
||||||
# App imports
|
# App imports
|
||||||
from audacity_controller import AudacityController
|
from audacity_controller import AudacityController
|
||||||
@ -52,67 +54,100 @@ if TYPE_CHECKING:
|
|||||||
from musicmuster import Window
|
from musicmuster import Window
|
||||||
|
|
||||||
|
|
||||||
class EscapeDelegate(QStyledItemDelegate):
|
class PlaylistDelegate(QStyledItemDelegate):
|
||||||
"""
|
"""
|
||||||
- increases the height of a row when editing to make editing easier
|
|
||||||
- closes the edit on control-return
|
- closes the edit on control-return
|
||||||
- checks with user before abandoning edit on Escape
|
- checks with user before abandoning edit on Escape
|
||||||
- positions cursor where double-click occurs
|
- positions cursor where double-click occurs
|
||||||
|
- expands edit box and parent table row as text is added
|
||||||
|
|
||||||
|
Parts inspired by https://stackoverflow.com/questions/69113867/
|
||||||
|
make-row-of-qtableview-expand-as-editor-grows-in-height
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
class EditorDocument(QTextDocument):
|
||||||
|
def __init__(self, parent):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.setDocumentMargin(0)
|
||||||
|
self.contentsChange.connect(self.contents_change)
|
||||||
|
self.height = None
|
||||||
|
parent.setDocument(self)
|
||||||
|
|
||||||
|
def contents_change(self, position, chars_removed, chars_added):
|
||||||
|
def resize_func():
|
||||||
|
if self.size().height() != self.height:
|
||||||
|
doc_size = self.size()
|
||||||
|
self.parent().resize(int(doc_size.width()), int(doc_size.height()))
|
||||||
|
|
||||||
|
QTimer.singleShot(0, resize_func)
|
||||||
|
|
||||||
def __init__(self, parent: QWidget, source_model: PlaylistModel) -> None:
|
def __init__(self, parent: QWidget, source_model: PlaylistModel) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.source_model = source_model
|
self.source_model = source_model
|
||||||
self.signals = MusicMusterSignals()
|
self.signals = MusicMusterSignals()
|
||||||
self.click_position = None # Store the mouse click position
|
self.click_position = None
|
||||||
|
self.current_editor: Optional[Any] = None
|
||||||
|
|
||||||
def createEditor(
|
def createEditor(
|
||||||
self,
|
self,
|
||||||
parent: Optional[QWidget],
|
parent: Optional[QWidget],
|
||||||
option: QStyleOptionViewItem,
|
option: QStyleOptionViewItem,
|
||||||
index: QModelIndex,
|
index: QModelIndex,
|
||||||
) -> Optional[QDoubleSpinBox | QPlainTextEdit]:
|
) -> Optional[QDoubleSpinBox | QTextEdit]:
|
||||||
"""
|
"""
|
||||||
Intercept createEditor call and make row just a little bit taller
|
Intercept createEditor call and make row just a little bit taller
|
||||||
"""
|
"""
|
||||||
|
|
||||||
editor: QDoubleSpinBox | QPlainTextEdit
|
editor: QDoubleSpinBox | QTextEdit
|
||||||
|
|
||||||
|
class Editor(QTextEdit):
|
||||||
|
def resizeEvent(self, event):
|
||||||
|
super().resizeEvent(event)
|
||||||
|
parent.parent().resizeRowToContents(index.row())
|
||||||
|
|
||||||
self.signals = MusicMusterSignals()
|
self.signals = MusicMusterSignals()
|
||||||
self.signals.enable_escape_signal.emit(False)
|
self.signals.enable_escape_signal.emit(False)
|
||||||
|
|
||||||
if isinstance(self.parent(), PlaylistTab):
|
if self.current_editor:
|
||||||
p = cast(PlaylistTab, self.parent())
|
editor = self.current_editor
|
||||||
|
else:
|
||||||
if index.column() == Col.INTRO.value:
|
if index.column() == Col.INTRO.value:
|
||||||
editor = QDoubleSpinBox(parent)
|
editor = QDoubleSpinBox(parent)
|
||||||
editor.setDecimals(1)
|
editor.setDecimals(1)
|
||||||
editor.setSingleStep(0.1)
|
editor.setSingleStep(0.1)
|
||||||
return editor
|
return editor
|
||||||
elif isinstance(index.data(), str):
|
elif isinstance(index.data(), str):
|
||||||
editor = QPlainTextEdit(parent)
|
editor = Editor(parent)
|
||||||
editor.setGeometry(option.rect) # Match the cell geometry
|
editor.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
||||||
row = index.row()
|
editor.setFrameShape(QFrame.Shape.NoFrame)
|
||||||
row_height = p.rowHeight(row)
|
self.current_editor = editor
|
||||||
p.setRowHeight(row, row_height + Config.MINIMUM_ROW_HEIGHT)
|
PlaylistDelegate.EditorDocument(editor)
|
||||||
return editor
|
return editor
|
||||||
|
|
||||||
return super().createEditor(parent, option, index)
|
|
||||||
|
|
||||||
def destroyEditor(self, editor: Optional[QWidget], index: QModelIndex) -> None:
|
def destroyEditor(self, editor: Optional[QWidget], index: QModelIndex) -> None:
|
||||||
"""
|
"""
|
||||||
Intercept editor destroyment
|
Intercept editor destroyment
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
super().destroyEditor(editor, index)
|
||||||
|
self.current_editor = None
|
||||||
|
# Funky mypy dancing:
|
||||||
|
parent = self.parent()
|
||||||
|
if parent and hasattr(parent, "resizeRowToContents"):
|
||||||
|
parent.resizeRowToContents(index.row())
|
||||||
self.signals.enable_escape_signal.emit(True)
|
self.signals.enable_escape_signal.emit(True)
|
||||||
return super().destroyEditor(editor, index)
|
|
||||||
|
|
||||||
def editorEvent(
|
def editorEvent(
|
||||||
self, event: QEvent, model: QAbstractItemModel, option, index: QModelIndex
|
self,
|
||||||
|
event: Optional[QEvent],
|
||||||
|
model: Optional[QAbstractItemModel],
|
||||||
|
option: QStyleOptionViewItem,
|
||||||
|
index: QModelIndex,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Capture mouse click position."""
|
"""Capture mouse click position."""
|
||||||
|
|
||||||
if event.type() == QEvent.Type.MouseButtonPress:
|
if event and event.type() == QEvent.Type.MouseButtonPress:
|
||||||
|
if hasattr(event, "pos"):
|
||||||
self.click_position = event.pos()
|
self.click_position = event.pos()
|
||||||
return super().editorEvent(event, model, option, index)
|
return super().editorEvent(event, model, option, index)
|
||||||
|
|
||||||
@ -120,10 +155,10 @@ class EscapeDelegate(QStyledItemDelegate):
|
|||||||
"""By default, QPlainTextEdit doesn't handle enter or return"""
|
"""By default, QPlainTextEdit doesn't handle enter or return"""
|
||||||
|
|
||||||
if editor is None or event is None:
|
if editor is None or event is None:
|
||||||
return super().eventFilter(editor, event)
|
return False
|
||||||
|
|
||||||
if event.type() == QEvent.Type.Show:
|
if event.type() == QEvent.Type.Show:
|
||||||
if self.click_position and isinstance(editor, QPlainTextEdit):
|
if self.click_position and isinstance(editor, QTextEdit):
|
||||||
# Map click position to editor's local space
|
# Map click position to editor's local space
|
||||||
local_click_position = editor.mapFromParent(self.click_position)
|
local_click_position = editor.mapFromParent(self.click_position)
|
||||||
|
|
||||||
@ -134,24 +169,27 @@ class EscapeDelegate(QStyledItemDelegate):
|
|||||||
# Reset click position
|
# Reset click position
|
||||||
self.click_position = None
|
self.click_position = None
|
||||||
|
|
||||||
return super().eventFilter(editor, event)
|
return False
|
||||||
|
|
||||||
elif event.type() == QEvent.Type.KeyPress:
|
elif event.type() == QEvent.Type.KeyPress:
|
||||||
key_event = cast(QKeyEvent, event)
|
key_event = cast(QKeyEvent, event)
|
||||||
if key_event.key() == Qt.Key.Key_Return:
|
key = key_event.key()
|
||||||
|
if key == Qt.Key.Key_Return:
|
||||||
if key_event.modifiers() == (Qt.KeyboardModifier.ControlModifier):
|
if key_event.modifiers() == (Qt.KeyboardModifier.ControlModifier):
|
||||||
self.commitData.emit(editor)
|
self.commitData.emit(editor)
|
||||||
self.closeEditor.emit(editor)
|
self.closeEditor.emit(editor)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
elif key_event.key() == Qt.Key.Key_Escape:
|
elif key == Qt.Key.Key_Escape:
|
||||||
# Close editor if no changes have been made
|
# Close editor if no changes have been made
|
||||||
data_modified = False
|
data_modified = False
|
||||||
if isinstance(editor, QPlainTextEdit):
|
if isinstance(editor, QTextEdit):
|
||||||
data_modified = self.original_model_data != editor.toPlainText()
|
data_modified = (
|
||||||
|
self.original_model_data.value() != editor.toPlainText()
|
||||||
|
)
|
||||||
elif isinstance(editor, QDoubleSpinBox):
|
elif isinstance(editor, QDoubleSpinBox):
|
||||||
data_modified = (
|
data_modified = (
|
||||||
self.original_model_data != int(editor.value()) * 1000
|
self.original_model_data.value() != int(editor.value()) * 1000
|
||||||
)
|
)
|
||||||
if not data_modified:
|
if not data_modified:
|
||||||
self.closeEditor.emit(editor)
|
self.closeEditor.emit(editor)
|
||||||
@ -166,6 +204,36 @@ class EscapeDelegate(QStyledItemDelegate):
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def sizeHint(self, option, index):
|
||||||
|
self.initStyleOption(option, index)
|
||||||
|
if self.current_editor:
|
||||||
|
doc = self.current_editor.document()
|
||||||
|
else:
|
||||||
|
doc = QTextDocument()
|
||||||
|
doc.setTextWidth(option.rect.width())
|
||||||
|
doc.setDefaultFont(option.font)
|
||||||
|
doc.setDocumentMargin(0)
|
||||||
|
doc.setHtml(option.text)
|
||||||
|
|
||||||
|
# For debugging +++
|
||||||
|
# Calculate sizes
|
||||||
|
# document_size = doc.documentLayout().documentSize()
|
||||||
|
# ideal_width = doc.idealWidth()
|
||||||
|
# height = document_size.height()
|
||||||
|
# rect_width = option.rect.width()
|
||||||
|
# text = option.text
|
||||||
|
|
||||||
|
# # Debug output
|
||||||
|
# print(f"Index: {index.row()}, {index.column()}")
|
||||||
|
# print(f"Text: {text}")
|
||||||
|
# print(f"Option.rect width: {rect_width}")
|
||||||
|
# print(f"Document idealWidth: {ideal_width}")
|
||||||
|
# print(f"Document height: {height}")
|
||||||
|
# print(f"---")
|
||||||
|
# --- For debugging
|
||||||
|
|
||||||
|
return QSize(int(doc.idealWidth()), int(doc.size().height()))
|
||||||
|
|
||||||
def setEditorData(self, editor, index):
|
def setEditorData(self, editor, index):
|
||||||
proxy_model = index.model()
|
proxy_model = index.model()
|
||||||
edit_index = proxy_model.mapToSource(index)
|
edit_index = proxy_model.mapToSource(index)
|
||||||
@ -174,6 +242,7 @@ class EscapeDelegate(QStyledItemDelegate):
|
|||||||
edit_index, Qt.ItemDataRole.EditRole
|
edit_index, Qt.ItemDataRole.EditRole
|
||||||
)
|
)
|
||||||
if index.column() == Col.INTRO.value:
|
if index.column() == Col.INTRO.value:
|
||||||
|
if self.original_model_data.value():
|
||||||
editor.setValue(self.original_model_data.value() / 1000)
|
editor.setValue(self.original_model_data.value() / 1000)
|
||||||
else:
|
else:
|
||||||
editor.setPlainText(self.original_model_data.value())
|
editor.setPlainText(self.original_model_data.value())
|
||||||
@ -182,7 +251,7 @@ class EscapeDelegate(QStyledItemDelegate):
|
|||||||
proxy_model = index.model()
|
proxy_model = index.model()
|
||||||
edit_index = proxy_model.mapToSource(index)
|
edit_index = proxy_model.mapToSource(index)
|
||||||
|
|
||||||
if isinstance(editor, QPlainTextEdit):
|
if isinstance(editor, QTextEdit):
|
||||||
value = editor.toPlainText().strip()
|
value = editor.toPlainText().strip()
|
||||||
elif isinstance(editor, QDoubleSpinBox):
|
elif isinstance(editor, QDoubleSpinBox):
|
||||||
value = editor.value()
|
value = editor.value()
|
||||||
@ -230,7 +299,7 @@ class PlaylistTab(QTableView):
|
|||||||
# Set up widget
|
# Set up widget
|
||||||
self.source_model = PlaylistModel(playlist_id)
|
self.source_model = PlaylistModel(playlist_id)
|
||||||
self.proxy_model = PlaylistProxyModel(self.source_model)
|
self.proxy_model = PlaylistProxyModel(self.source_model)
|
||||||
self.setItemDelegate(EscapeDelegate(self, self.source_model))
|
self.setItemDelegate(PlaylistDelegate(self, self.source_model))
|
||||||
self.setAlternatingRowColors(True)
|
self.setAlternatingRowColors(True)
|
||||||
self.setVerticalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel)
|
self.setVerticalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel)
|
||||||
self.setDragDropMode(QAbstractItemView.DragDropMode.InternalMove)
|
self.setDragDropMode(QAbstractItemView.DragDropMode.InternalMove)
|
||||||
@ -264,7 +333,7 @@ class PlaylistTab(QTableView):
|
|||||||
|
|
||||||
# Set up for Audacity
|
# Set up for Audacity
|
||||||
try:
|
try:
|
||||||
self.ac = AudacityController()
|
self.ac: Optional[AudacityController] = AudacityController()
|
||||||
except ApplicationError as e:
|
except ApplicationError as e:
|
||||||
self.ac = None
|
self.ac = None
|
||||||
show_warning(self.musicmuster, "Audacity error", str(e))
|
show_warning(self.musicmuster, "Audacity error", str(e))
|
||||||
@ -272,9 +341,15 @@ class PlaylistTab(QTableView):
|
|||||||
# Stretch last column *after* setting column widths which is
|
# Stretch last column *after* setting column widths which is
|
||||||
# *much* faster
|
# *much* faster
|
||||||
h_header = self.horizontalHeader()
|
h_header = self.horizontalHeader()
|
||||||
if isinstance(h_header, QHeaderView):
|
if h_header:
|
||||||
h_header.sectionResized.connect(self._column_resize)
|
h_header.sectionResized.connect(self._column_resize)
|
||||||
h_header.setStretchLastSection(True)
|
h_header.setStretchLastSection(True)
|
||||||
|
# Resize on vertical header click
|
||||||
|
v_header = self.verticalHeader()
|
||||||
|
if v_header:
|
||||||
|
v_header.setMinimumSectionSize(5)
|
||||||
|
v_header.sectionHandleDoubleClicked.disconnect()
|
||||||
|
v_header.sectionHandleDoubleClicked.connect(self.resizeRowToContents)
|
||||||
|
|
||||||
# Setting ResizeToContents causes screen flash on load
|
# Setting ResizeToContents causes screen flash on load
|
||||||
self.resize_rows()
|
self.resize_rows()
|
||||||
@ -302,10 +377,15 @@ class PlaylistTab(QTableView):
|
|||||||
# Deselect edited line
|
# Deselect edited line
|
||||||
self.clear_selection()
|
self.clear_selection()
|
||||||
|
|
||||||
def dropEvent(self, event):
|
@line_profiler.profile
|
||||||
|
def dropEvent(
|
||||||
|
self, event: Optional[QDropEvent], dummy_for_profiling: Optional[int] = None
|
||||||
|
) -> None:
|
||||||
|
if not event:
|
||||||
|
return
|
||||||
if event.source() is not self or (
|
if event.source() is not self or (
|
||||||
event.dropAction() != Qt.DropAction.MoveAction
|
event.dropAction() != Qt.DropAction.MoveAction
|
||||||
and self.dragDropMode() != QAbstractItemView.InternalMove
|
and self.dragDropMode() != QAbstractItemView.DragDropMode.InternalMove
|
||||||
):
|
):
|
||||||
super().dropEvent(event)
|
super().dropEvent(event)
|
||||||
|
|
||||||
@ -370,6 +450,16 @@ class PlaylistTab(QTableView):
|
|||||||
self.reset()
|
self.reset()
|
||||||
super().mouseReleaseEvent(event)
|
super().mouseReleaseEvent(event)
|
||||||
|
|
||||||
|
def resizeRowToContents(self, row):
|
||||||
|
super().resizeRowToContents(row)
|
||||||
|
self.verticalHeader().resizeSection(row, self.sizeHintForRow(row))
|
||||||
|
|
||||||
|
def resizeRowsToContents(self):
|
||||||
|
header = self.verticalHeader()
|
||||||
|
for row in range(self.model().rowCount()):
|
||||||
|
hint = self.sizeHintForRow(row)
|
||||||
|
header.resizeSection(row, hint)
|
||||||
|
|
||||||
def selectionChanged(
|
def selectionChanged(
|
||||||
self, selected: QItemSelection, deselected: QItemSelection
|
self, selected: QItemSelection, deselected: QItemSelection
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -483,9 +573,7 @@ class PlaylistTab(QTableView):
|
|||||||
self._add_context_menu(
|
self._add_context_menu(
|
||||||
"Rescan track", lambda: self._rescan(model_row_number)
|
"Rescan track", lambda: self._rescan(model_row_number)
|
||||||
)
|
)
|
||||||
self._add_context_menu(
|
self._add_context_menu("Mark for moving", lambda: self._mark_for_moving())
|
||||||
"Mark for moving", lambda: self._mark_for_moving()
|
|
||||||
)
|
|
||||||
if self.musicmuster.move_source_rows:
|
if self.musicmuster.move_source_rows:
|
||||||
self._add_context_menu(
|
self._add_context_menu(
|
||||||
"Move selected rows here", lambda: self._move_selected_rows()
|
"Move selected rows here", lambda: self._move_selected_rows()
|
||||||
@ -718,6 +806,8 @@ class PlaylistTab(QTableView):
|
|||||||
Import current Audacity track to passed row
|
Import current Audacity track to passed row
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if not self.ac:
|
||||||
|
return
|
||||||
try:
|
try:
|
||||||
self.ac.export()
|
self.ac.export()
|
||||||
self._rescan(row_number)
|
self._rescan(row_number)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user