feat: hide library toolbar buttons option, Ctrl+O hotkey, iTunes-style equalizer animation on playing track
This commit is contained in:
parent
ec797b8074
commit
d153d69bfd
@ -142,6 +142,8 @@ class ConfigLoader:
|
|||||||
self.config.set('Advanced', 'clean_temp', 'true')
|
self.config.set('Advanced', 'clean_temp', 'true')
|
||||||
if not self.config.has_option('Advanced', 'show_download'):
|
if not self.config.has_option('Advanced', 'show_download'):
|
||||||
self.config.set('Advanced', 'show_download', 'false')
|
self.config.set('Advanced', 'show_download', 'false')
|
||||||
|
if not self.config.has_option('Advanced', 'hide_library_buttons'):
|
||||||
|
self.config.set('Advanced', 'hide_library_buttons', 'false')
|
||||||
|
|
||||||
# Hotkeys section
|
# Hotkeys section
|
||||||
if not self.config.has_section('Hotkeys'):
|
if not self.config.has_section('Hotkeys'):
|
||||||
@ -165,6 +167,7 @@ class ConfigLoader:
|
|||||||
'ipod_refresh': 'Ctrl+R',
|
'ipod_refresh': 'Ctrl+R',
|
||||||
'download': 'Ctrl+Enter',
|
'download': 'Ctrl+Enter',
|
||||||
'delete_selected': 'Delete',
|
'delete_selected': 'Delete',
|
||||||
|
'library_add_files': 'Ctrl+O',
|
||||||
}
|
}
|
||||||
for key, value in hotkey_defaults.items():
|
for key, value in hotkey_defaults.items():
|
||||||
if not self.config.has_option('Hotkeys', key):
|
if not self.config.has_option('Hotkeys', key):
|
||||||
|
|||||||
@ -95,6 +95,7 @@ DEFAULTS = [
|
|||||||
("download", "Ctrl+Enter", "Start Download", "global"),
|
("download", "Ctrl+Enter", "Start Download", "global"),
|
||||||
("delete_selected", "Delete", "Delete Selected", "global"),
|
("delete_selected", "Delete", "Delete Selected", "global"),
|
||||||
("edit_metadata", "F2", "Edit Metadata", "global"),
|
("edit_metadata", "F2", "Edit Metadata", "global"),
|
||||||
|
("library_add_files", "Ctrl+O", "Library: Add Files", "global"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
202
src/main.py
202
src/main.py
@ -15,10 +15,11 @@ from PyQt6.QtWidgets import (
|
|||||||
QPushButton, QLabel, QLineEdit, QProgressBar, QFileDialog,
|
QPushButton, QLabel, QLineEdit, QProgressBar, QFileDialog,
|
||||||
QComboBox, QCheckBox, QTabWidget, QListWidget, QListWidgetItem,
|
QComboBox, QCheckBox, QTabWidget, QListWidget, QListWidgetItem,
|
||||||
QMessageBox, QSpinBox, QGroupBox, QHeaderView,
|
QMessageBox, QSpinBox, QGroupBox, QHeaderView,
|
||||||
QTableWidget, QTableWidgetItem, QSlider, QMenu, QDialog
|
QTableWidget, QTableWidgetItem, QSlider, QMenu, QDialog, QStyle,
|
||||||
|
QStyleOptionSlider
|
||||||
)
|
)
|
||||||
from PyQt6.QtCore import Qt, QThread, pyqtSignal, QUrl
|
from PyQt6.QtCore import Qt, QThread, pyqtSignal, QUrl, QTimer
|
||||||
from PyQt6.QtGui import QPixmap
|
from PyQt6.QtGui import QPixmap, QColor
|
||||||
from PyQt6.QtMultimedia import QMediaPlayer, QAudioOutput
|
from PyQt6.QtMultimedia import QMediaPlayer, QAudioOutput
|
||||||
|
|
||||||
from youtube_downloader import YouTubeDownloader, TrackInfo
|
from youtube_downloader import YouTubeDownloader, TrackInfo
|
||||||
@ -38,6 +39,23 @@ logger = logging.getLogger(__name__)
|
|||||||
AUDIO_EXTS = {'.m4a', '.mp3', '.aac'}
|
AUDIO_EXTS = {'.m4a', '.mp3', '.aac'}
|
||||||
SOURCE_EXTS = {'.flac', '.wav', '.ogg', '.opus', '.wma', '.ape', '.aiff', '.alac'}
|
SOURCE_EXTS = {'.flac', '.wav', '.ogg', '.opus', '.wma', '.ape', '.aiff', '.alac'}
|
||||||
|
|
||||||
|
EQ_FRAMES = [
|
||||||
|
"\u2581\u2583\u2585",
|
||||||
|
"\u2582\u2585\u2587",
|
||||||
|
"\u2583\u2587\u2586",
|
||||||
|
"\u2585\u2586\u2584",
|
||||||
|
"\u2586\u2584\u2583",
|
||||||
|
"\u2587\u2583\u2582",
|
||||||
|
"\u2586\u2582\u2581",
|
||||||
|
"\u2585\u2581\u2583",
|
||||||
|
"\u2583\u2582\u2585",
|
||||||
|
"\u2581\u2583\u2587",
|
||||||
|
"\u2582\u2585\u2586",
|
||||||
|
"\u2583\u2587\u2584",
|
||||||
|
"\u2585\u2586\u2583",
|
||||||
|
"\u2586\u2584\u2581",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class WorkerThread(QThread):
|
class WorkerThread(QThread):
|
||||||
"""Worker thread for background tasks"""
|
"""Worker thread for background tasks"""
|
||||||
@ -268,6 +286,31 @@ class WorkerThread(QThread):
|
|||||||
self.wait()
|
self.wait()
|
||||||
|
|
||||||
|
|
||||||
|
class JumpSlider(QSlider):
|
||||||
|
def _jump_to_pos(self, x):
|
||||||
|
opt = QStyleOptionSlider()
|
||||||
|
self.initStyleOption(opt)
|
||||||
|
gr = self.style().subControlRect(
|
||||||
|
QStyle.ComplexControl.CC_Slider, opt,
|
||||||
|
QStyle.SubControl.SC_SliderGroove, self
|
||||||
|
)
|
||||||
|
val = QStyle.sliderValueFromPosition(
|
||||||
|
self.minimum(), self.maximum(),
|
||||||
|
int(x) - gr.x(), gr.width()
|
||||||
|
)
|
||||||
|
self.setValue(val)
|
||||||
|
|
||||||
|
def mousePressEvent(self, ev):
|
||||||
|
if ev.button() == Qt.MouseButton.LeftButton:
|
||||||
|
self._jump_to_pos(ev.position().x())
|
||||||
|
super().mousePressEvent(ev)
|
||||||
|
|
||||||
|
def mouseMoveEvent(self, ev):
|
||||||
|
if ev.buttons() & Qt.MouseButton.LeftButton:
|
||||||
|
self._jump_to_pos(ev.position().x())
|
||||||
|
super().mouseMoveEvent(ev)
|
||||||
|
|
||||||
|
|
||||||
class MainWindow(QMainWindow):
|
class MainWindow(QMainWindow):
|
||||||
"""Main application window"""
|
"""Main application window"""
|
||||||
|
|
||||||
@ -285,6 +328,8 @@ class MainWindow(QMainWindow):
|
|||||||
self.ipod_devices: List[Dict] = []
|
self.ipod_devices: List[Dict] = []
|
||||||
self.current_mount_point: Optional[str] = None
|
self.current_mount_point: Optional[str] = None
|
||||||
self.show_download_tab = False
|
self.show_download_tab = False
|
||||||
|
self.hide_library_buttons = False
|
||||||
|
self._library_toolbar_buttons: list = []
|
||||||
self.ipod_tab_index = 2
|
self.ipod_tab_index = 2
|
||||||
|
|
||||||
self.config_loader = ConfigLoader()
|
self.config_loader = ConfigLoader()
|
||||||
@ -293,6 +338,9 @@ class MainWindow(QMainWindow):
|
|||||||
self.player: Optional[QMediaPlayer] = None
|
self.player: Optional[QMediaPlayer] = None
|
||||||
self.audio_output: Optional[QAudioOutput] = None
|
self.audio_output: Optional[QAudioOutput] = None
|
||||||
self.current_playback_index: int = -1
|
self.current_playback_index: int = -1
|
||||||
|
self._eq_timer: Optional[QTimer] = None
|
||||||
|
self._eq_frame: int = 0
|
||||||
|
self._eq_running: bool = False
|
||||||
self._is_playing = False
|
self._is_playing = False
|
||||||
self._saved_volume: int = 80
|
self._saved_volume: int = 80
|
||||||
self._saved_track_path: str = ""
|
self._saved_track_path: str = ""
|
||||||
@ -351,6 +399,10 @@ class MainWindow(QMainWindow):
|
|||||||
self.show_download_tab = show_download
|
self.show_download_tab = show_download
|
||||||
self.tab_widget.setTabVisible(self.download_tab_index, show_download)
|
self.tab_widget.setTabVisible(self.download_tab_index, show_download)
|
||||||
|
|
||||||
|
hide_library_buttons = config.get_boolean("Advanced", "hide_library_buttons", fallback=False)
|
||||||
|
self.hide_library_buttons_check.setChecked(hide_library_buttons)
|
||||||
|
self._on_hide_library_buttons_toggled(hide_library_buttons)
|
||||||
|
|
||||||
self._saved_volume = config.get_int("Playback", "last_volume", fallback=80)
|
self._saved_volume = config.get_int("Playback", "last_volume", fallback=80)
|
||||||
self._saved_track_path = config.get("Playback", "last_track_path", fallback="")
|
self._saved_track_path = config.get("Playback", "last_track_path", fallback="")
|
||||||
self._saved_position_ms = config.get_int("Playback", "last_position_ms", fallback=0)
|
self._saved_position_ms = config.get_int("Playback", "last_position_ms", fallback=0)
|
||||||
@ -368,6 +420,7 @@ class MainWindow(QMainWindow):
|
|||||||
config.set("Advanced", "clean_temp", str(self.clean_temp_check.isChecked()).lower())
|
config.set("Advanced", "clean_temp", str(self.clean_temp_check.isChecked()).lower())
|
||||||
config.set("Device", "auto_detect", str(self.auto_detect_check.isChecked()).lower())
|
config.set("Device", "auto_detect", str(self.auto_detect_check.isChecked()).lower())
|
||||||
config.set("Advanced", "show_download", str(self.show_download_check.isChecked()).lower())
|
config.set("Advanced", "show_download", str(self.show_download_check.isChecked()).lower())
|
||||||
|
config.set("Advanced", "hide_library_buttons", str(self.hide_library_buttons_check.isChecked()).lower())
|
||||||
|
|
||||||
config.set("Playback", "last_volume", str(self.volume_slider.value()))
|
config.set("Playback", "last_volume", str(self.volume_slider.value()))
|
||||||
if self.current_playback_index >= 0 and self.player.source().isValid():
|
if self.current_playback_index >= 0 and self.player.source().isValid():
|
||||||
@ -409,6 +462,7 @@ class MainWindow(QMainWindow):
|
|||||||
"download": self._on_download_clicked,
|
"download": self._on_download_clicked,
|
||||||
"delete_selected": self._delete_selected_current,
|
"delete_selected": self._delete_selected_current,
|
||||||
"edit_metadata": self._on_edit_metadata,
|
"edit_metadata": self._on_edit_metadata,
|
||||||
|
"library_add_files": self._on_library_add_files,
|
||||||
})
|
})
|
||||||
self._refresh_shortcuts_table()
|
self._refresh_shortcuts_table()
|
||||||
|
|
||||||
@ -522,12 +576,13 @@ class MainWindow(QMainWindow):
|
|||||||
self.time_label_start.setFixedWidth(40)
|
self.time_label_start.setFixedWidth(40)
|
||||||
self.time_label_start.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
|
self.time_label_start.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
|
||||||
|
|
||||||
self.position_slider = QSlider(Qt.Orientation.Horizontal)
|
self.position_slider = JumpSlider(Qt.Orientation.Horizontal)
|
||||||
self.position_slider.setMinimum(0)
|
self.position_slider.setMinimum(0)
|
||||||
self.position_slider.setMaximum(1000)
|
self.position_slider.setMaximum(1000)
|
||||||
self.position_slider.setValue(0)
|
self.position_slider.setValue(0)
|
||||||
self.position_slider.sliderPressed.connect(self._on_slider_pressed)
|
self.position_slider.sliderPressed.connect(self._on_slider_pressed)
|
||||||
self.position_slider.sliderReleased.connect(self._on_slider_released)
|
self.position_slider.sliderMoved.connect(self._on_slider_pressed)
|
||||||
|
self.position_slider.sliderReleased.connect(self._on_slider_pressed)
|
||||||
|
|
||||||
self.time_label_end = QLabel("0:00")
|
self.time_label_end = QLabel("0:00")
|
||||||
self.time_label_end.setFixedWidth(40)
|
self.time_label_end.setFixedWidth(40)
|
||||||
@ -633,6 +688,15 @@ class MainWindow(QMainWindow):
|
|||||||
refresh_btn.clicked.connect(self._on_refresh_library_clicked)
|
refresh_btn.clicked.connect(self._on_refresh_library_clicked)
|
||||||
toolbar.addWidget(refresh_btn)
|
toolbar.addWidget(refresh_btn)
|
||||||
|
|
||||||
|
self._library_toolbar_buttons = [
|
||||||
|
self.library_add_btn,
|
||||||
|
self.library_convert_btn,
|
||||||
|
self.library_transfer_btn,
|
||||||
|
self.library_remove_btn,
|
||||||
|
self.library_edit_btn,
|
||||||
|
refresh_btn,
|
||||||
|
]
|
||||||
|
|
||||||
toolbar.addStretch()
|
toolbar.addStretch()
|
||||||
|
|
||||||
self.library_search_input = QLineEdit()
|
self.library_search_input = QLineEdit()
|
||||||
@ -786,6 +850,11 @@ class MainWindow(QMainWindow):
|
|||||||
self.show_download_check.toggled.connect(self._on_show_download_toggled)
|
self.show_download_check.toggled.connect(self._on_show_download_toggled)
|
||||||
advanced_layout.addWidget(self.show_download_check)
|
advanced_layout.addWidget(self.show_download_check)
|
||||||
|
|
||||||
|
self.hide_library_buttons_check = QCheckBox("Hide Library Toolbar Buttons")
|
||||||
|
self.hide_library_buttons_check.setChecked(False)
|
||||||
|
self.hide_library_buttons_check.toggled.connect(self._on_hide_library_buttons_toggled)
|
||||||
|
advanced_layout.addWidget(self.hide_library_buttons_check)
|
||||||
|
|
||||||
layout.addWidget(advanced_group)
|
layout.addWidget(advanced_group)
|
||||||
|
|
||||||
shortcuts_group = QGroupBox("Keyboard Shortcuts")
|
shortcuts_group = QGroupBox("Keyboard Shortcuts")
|
||||||
@ -824,6 +893,11 @@ class MainWindow(QMainWindow):
|
|||||||
self.show_download_tab = checked
|
self.show_download_tab = checked
|
||||||
self.tab_widget.setTabVisible(self.download_tab_index, checked)
|
self.tab_widget.setTabVisible(self.download_tab_index, checked)
|
||||||
|
|
||||||
|
def _on_hide_library_buttons_toggled(self, checked: bool):
|
||||||
|
self.hide_library_buttons = checked
|
||||||
|
for btn in self._library_toolbar_buttons:
|
||||||
|
btn.setVisible(not checked)
|
||||||
|
|
||||||
def _setup_player(self):
|
def _setup_player(self):
|
||||||
"""Initialize QMediaPlayer and QAudioOutput for playback."""
|
"""Initialize QMediaPlayer and QAudioOutput for playback."""
|
||||||
self.player = QMediaPlayer()
|
self.player = QMediaPlayer()
|
||||||
@ -835,6 +909,10 @@ class MainWindow(QMainWindow):
|
|||||||
self.player.durationChanged.connect(self._on_media_duration_changed)
|
self.player.durationChanged.connect(self._on_media_duration_changed)
|
||||||
self.player.mediaStatusChanged.connect(self._on_media_status_changed)
|
self.player.mediaStatusChanged.connect(self._on_media_status_changed)
|
||||||
|
|
||||||
|
self._eq_timer = QTimer()
|
||||||
|
self._eq_timer.setInterval(80)
|
||||||
|
self._eq_timer.timeout.connect(self._tick_eq_animation)
|
||||||
|
|
||||||
def _play_track_from_index(self, index: int, start_position_ms: int = 0, auto_play: bool = True):
|
def _play_track_from_index(self, index: int, start_position_ms: int = 0, auto_play: bool = True):
|
||||||
"""Start playing a track from the library table by row index."""
|
"""Start playing a track from the library table by row index."""
|
||||||
if index < 0 or index >= self.library_table.rowCount():
|
if index < 0 or index >= self.library_table.rowCount():
|
||||||
@ -846,6 +924,9 @@ class MainWindow(QMainWindow):
|
|||||||
if not track_data or not os.path.exists(track_data.get("path", "")):
|
if not track_data or not os.path.exists(track_data.get("path", "")):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
old_index = self.current_playback_index
|
||||||
|
self._clear_playing_row(old_index)
|
||||||
|
|
||||||
self.current_playback_index = index
|
self.current_playback_index = index
|
||||||
self.player.setSource(QUrl.fromLocalFile(track_data["path"]))
|
self.player.setSource(QUrl.fromLocalFile(track_data["path"]))
|
||||||
if auto_play:
|
if auto_play:
|
||||||
@ -881,6 +962,10 @@ class MainWindow(QMainWindow):
|
|||||||
self.library_table.clearSelection()
|
self.library_table.clearSelection()
|
||||||
self.library_table.selectRow(index)
|
self.library_table.selectRow(index)
|
||||||
|
|
||||||
|
self._set_playing_row_bg(index)
|
||||||
|
if auto_play:
|
||||||
|
self._start_eq_animation()
|
||||||
|
|
||||||
if start_position_ms > 0:
|
if start_position_ms > 0:
|
||||||
self._pending_seek_ms = start_position_ms
|
self._pending_seek_ms = start_position_ms
|
||||||
else:
|
else:
|
||||||
@ -893,6 +978,7 @@ class MainWindow(QMainWindow):
|
|||||||
self.player.pause()
|
self.player.pause()
|
||||||
self._is_playing = False
|
self._is_playing = False
|
||||||
self.play_btn.setText("\u25B6")
|
self.play_btn.setText("\u25B6")
|
||||||
|
self._stop_eq_animation()
|
||||||
else:
|
else:
|
||||||
if self.player.playbackState() == QMediaPlayer.PlaybackState.StoppedState:
|
if self.player.playbackState() == QMediaPlayer.PlaybackState.StoppedState:
|
||||||
# Nothing playing yet — play first selected row or row 0
|
# Nothing playing yet — play first selected row or row 0
|
||||||
@ -903,6 +989,7 @@ class MainWindow(QMainWindow):
|
|||||||
self.player.play()
|
self.player.play()
|
||||||
self._is_playing = True
|
self._is_playing = True
|
||||||
self.play_btn.setText("\u23F8")
|
self.play_btn.setText("\u23F8")
|
||||||
|
self._start_eq_animation()
|
||||||
|
|
||||||
def _on_prev_clicked(self):
|
def _on_prev_clicked(self):
|
||||||
if self.player is None:
|
if self.player is None:
|
||||||
@ -978,8 +1065,10 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
def _on_media_status_changed(self, status):
|
def _on_media_status_changed(self, status):
|
||||||
if status == QMediaPlayer.MediaStatus.EndOfMedia:
|
if status == QMediaPlayer.MediaStatus.EndOfMedia:
|
||||||
|
self._stop_eq_animation()
|
||||||
self._on_next_clicked()
|
self._on_next_clicked()
|
||||||
elif status == QMediaPlayer.MediaStatus.NoMedia:
|
elif status == QMediaPlayer.MediaStatus.NoMedia:
|
||||||
|
self._stop_eq_animation()
|
||||||
self.play_btn.setText("\u25B6")
|
self.play_btn.setText("\u25B6")
|
||||||
self._is_playing = False
|
self._is_playing = False
|
||||||
elif status == QMediaPlayer.MediaStatus.LoadedMedia:
|
elif status == QMediaPlayer.MediaStatus.LoadedMedia:
|
||||||
@ -987,6 +1076,75 @@ class MainWindow(QMainWindow):
|
|||||||
self.player.setPosition(self._pending_seek_ms)
|
self.player.setPosition(self._pending_seek_ms)
|
||||||
self._pending_seek_ms = 0
|
self._pending_seek_ms = 0
|
||||||
|
|
||||||
|
def _start_eq_animation(self):
|
||||||
|
if not self._eq_timer or self._eq_running:
|
||||||
|
return
|
||||||
|
self._eq_running = True
|
||||||
|
self._eq_frame = 0
|
||||||
|
self._tick_eq_animation()
|
||||||
|
self._eq_timer.start()
|
||||||
|
|
||||||
|
def _stop_eq_animation(self):
|
||||||
|
if not self._eq_timer:
|
||||||
|
return
|
||||||
|
self._eq_timer.stop()
|
||||||
|
self._eq_running = False
|
||||||
|
idx = self.current_playback_index
|
||||||
|
if idx >= 0 and idx < self.library_table.rowCount():
|
||||||
|
item = self.library_table.item(idx, 0)
|
||||||
|
if item:
|
||||||
|
is_ready, data = item.data(Qt.ItemDataRole.UserRole)
|
||||||
|
track_num = data.get("track_num", 0) if data else 0
|
||||||
|
item.setText(str(track_num) if (is_ready and track_num) else "")
|
||||||
|
item.setData(Qt.ItemDataRole.ForegroundRole, None)
|
||||||
|
|
||||||
|
def _tick_eq_animation(self):
|
||||||
|
idx = self.current_playback_index
|
||||||
|
if idx < 0 or idx >= self.library_table.rowCount():
|
||||||
|
return
|
||||||
|
item = self.library_table.item(idx, 0)
|
||||||
|
if item is None:
|
||||||
|
return
|
||||||
|
glyph = EQ_FRAMES[self._eq_frame % len(EQ_FRAMES)]
|
||||||
|
self._eq_frame += 1
|
||||||
|
item.setText(glyph)
|
||||||
|
accent = self.palette().color(self.palette().ColorRole.Highlight)
|
||||||
|
item.setForeground(accent)
|
||||||
|
|
||||||
|
def _set_playing_row_bg(self, row: int):
|
||||||
|
if row < 0 or row >= self.library_table.rowCount():
|
||||||
|
return
|
||||||
|
bg = QColor(self.palette().color(self.palette().ColorRole.Highlight))
|
||||||
|
bg.setAlpha(50)
|
||||||
|
for col in range(self.library_table.columnCount()):
|
||||||
|
item = self.library_table.item(row, col)
|
||||||
|
if item:
|
||||||
|
item.setBackground(bg)
|
||||||
|
|
||||||
|
def _clear_playing_row_bg(self, row: int):
|
||||||
|
if row < 0 or row >= self.library_table.rowCount():
|
||||||
|
return
|
||||||
|
for col in range(self.library_table.columnCount()):
|
||||||
|
item = self.library_table.item(row, col)
|
||||||
|
if item:
|
||||||
|
item.setData(Qt.ItemDataRole.BackgroundRole, None)
|
||||||
|
|
||||||
|
def _clear_playing_row(self, row: int):
|
||||||
|
if row < 0 or row >= self.library_table.rowCount():
|
||||||
|
return
|
||||||
|
for col in range(self.library_table.columnCount()):
|
||||||
|
item = self.library_table.item(row, col)
|
||||||
|
if item:
|
||||||
|
item.setData(Qt.ItemDataRole.BackgroundRole, None)
|
||||||
|
self._eq_timer.stop() if self._eq_timer else None
|
||||||
|
self._eq_running = False
|
||||||
|
item0 = self.library_table.item(row, 0)
|
||||||
|
if item0:
|
||||||
|
is_ready, data = item0.data(Qt.ItemDataRole.UserRole)
|
||||||
|
track_num = data.get("track_num", 0) if data else 0
|
||||||
|
item0.setText(str(track_num) if (is_ready and track_num) else "")
|
||||||
|
item0.setData(Qt.ItemDataRole.ForegroundRole, None)
|
||||||
|
|
||||||
def _on_table_double_clicked(self, row: int):
|
def _on_table_double_clicked(self, row: int):
|
||||||
self._play_track_from_index(row)
|
self._play_track_from_index(row)
|
||||||
|
|
||||||
@ -1240,6 +1398,40 @@ class MainWindow(QMainWindow):
|
|||||||
status = f"{len(all_tracks)} track(s) \u2014 {', '.join(parts)}" if parts else "No tracks in library"
|
status = f"{len(all_tracks)} track(s) \u2014 {', '.join(parts)}" if parts else "No tracks in library"
|
||||||
self.library_status.setText(status)
|
self.library_status.setText(status)
|
||||||
|
|
||||||
|
self._restore_playing_row()
|
||||||
|
|
||||||
|
def _restore_playing_row(self):
|
||||||
|
idx = self.current_playback_index
|
||||||
|
if idx < 0:
|
||||||
|
return
|
||||||
|
track_path = ""
|
||||||
|
if self.player and self.player.source().isValid():
|
||||||
|
track_path = self.player.source().toLocalFile()
|
||||||
|
if not track_path and idx < self.library_table.rowCount():
|
||||||
|
item = self.library_table.item(idx, 0)
|
||||||
|
if item:
|
||||||
|
_, data = item.data(Qt.ItemDataRole.UserRole)
|
||||||
|
if data:
|
||||||
|
track_path = data.get("path", "")
|
||||||
|
|
||||||
|
if not track_path:
|
||||||
|
self.current_playback_index = -1
|
||||||
|
return
|
||||||
|
|
||||||
|
for row in range(self.library_table.rowCount()):
|
||||||
|
item = self.library_table.item(row, 0)
|
||||||
|
if item is None:
|
||||||
|
continue
|
||||||
|
_, data = item.data(Qt.ItemDataRole.UserRole)
|
||||||
|
if data and data.get("path") == track_path:
|
||||||
|
self.current_playback_index = row
|
||||||
|
self._set_playing_row_bg(row)
|
||||||
|
if self._is_playing:
|
||||||
|
self._start_eq_animation()
|
||||||
|
return
|
||||||
|
|
||||||
|
self.current_playback_index = -1
|
||||||
|
|
||||||
def _get_selected_ready_tracks(self) -> List[Tuple[TrackInfo, str]]:
|
def _get_selected_ready_tracks(self) -> List[Tuple[TrackInfo, str]]:
|
||||||
selected_rows = set()
|
selected_rows = set()
|
||||||
for item in self.library_table.selectedItems():
|
for item in self.library_table.selectedItems():
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user