Redesign Library tab and add Download tab toggle
Library tab redesign: - Merge 'Ready to Transfer' table and 'Source Files' list into one unified table - Replace bottom buttons with top toolbar (Add Files, Convert, Transfer, Remove, Refresh, Search) - Add search/filter bar to filter tracks by title - Add context menu (right-click): Play, Convert/Transfer, Show in File Manager, Remove - Source tracks shown in gray (dimmed), ready tracks in normal color - Remove status column with emoji — clean iTunes-like layout Settings: - Add 'Show YouTube Download Tab' checkbox in Advanced section - Download tab hidden by default, can be enabled in Settings - Uses QTabWidget.setTabVisible() to toggle without destroying widgets
This commit is contained in:
parent
885a401ad0
commit
8c576232b1
405
src/main.py
405
src/main.py
@ -15,7 +15,7 @@ from PyQt6.QtWidgets import (
|
||||
QPushButton, QLabel, QLineEdit, QProgressBar, QFileDialog,
|
||||
QComboBox, QCheckBox, QTabWidget, QListWidget, QListWidgetItem,
|
||||
QMessageBox, QSpinBox, QGroupBox, QHeaderView,
|
||||
QTableWidget, QTableWidgetItem, QSlider
|
||||
QTableWidget, QTableWidgetItem, QSlider, QMenu
|
||||
)
|
||||
from PyQt6.QtCore import Qt, QThread, pyqtSignal, QUrl
|
||||
from PyQt6.QtMultimedia import QMediaPlayer, QAudioOutput
|
||||
@ -219,8 +219,10 @@ class MainWindow(QMainWindow):
|
||||
self.downloaded_tracks: List[TrackInfo] = []
|
||||
self.library_ready: List[Dict] = []
|
||||
self.library_source_files: List[str] = []
|
||||
self.library_scanned_sources: List[Dict] = []
|
||||
self.ipod_devices: List[Dict] = []
|
||||
self.current_mount_point: Optional[str] = None
|
||||
self.show_download_tab = False
|
||||
|
||||
self.player: Optional[QMediaPlayer] = None
|
||||
self.audio_output: Optional[QAudioOutput] = None
|
||||
@ -248,25 +250,28 @@ class MainWindow(QMainWindow):
|
||||
main_widget = QWidget()
|
||||
main_layout = QVBoxLayout(main_widget)
|
||||
|
||||
tab_widget = QTabWidget()
|
||||
main_layout.addWidget(tab_widget)
|
||||
self.tab_widget = QTabWidget()
|
||||
main_layout.addWidget(self.tab_widget)
|
||||
|
||||
download_tab = QWidget()
|
||||
tab_widget.addTab(download_tab, "Download")
|
||||
self.tab_widget.addTab(download_tab, "Download")
|
||||
self._setup_download_tab(download_tab)
|
||||
self.download_tab_index = 0
|
||||
|
||||
library_tab = QWidget()
|
||||
tab_widget.addTab(library_tab, "Library")
|
||||
self.tab_widget.addTab(library_tab, "Library")
|
||||
self._setup_library_tab(library_tab)
|
||||
|
||||
ipod_tab = QWidget()
|
||||
tab_widget.addTab(ipod_tab, "iPod")
|
||||
self.tab_widget.addTab(ipod_tab, "iPod")
|
||||
self._setup_ipod_tab(ipod_tab)
|
||||
|
||||
settings_tab = QWidget()
|
||||
tab_widget.addTab(settings_tab, "Settings")
|
||||
self.tab_widget.addTab(settings_tab, "Settings")
|
||||
self._setup_settings_tab(settings_tab)
|
||||
|
||||
self.tab_widget.setTabVisible(self.download_tab_index, self.show_download_tab)
|
||||
|
||||
self.statusBar().showMessage("Ready")
|
||||
self.setCentralWidget(main_widget)
|
||||
|
||||
@ -384,30 +389,23 @@ class MainWindow(QMainWindow):
|
||||
|
||||
layout.addWidget(playback_group)
|
||||
|
||||
# Refresh button
|
||||
refresh_row = QHBoxLayout()
|
||||
refresh_btn = QPushButton("\u21BB Refresh Library")
|
||||
refresh_btn.clicked.connect(self._on_refresh_library_clicked)
|
||||
refresh_row.addStretch()
|
||||
refresh_row.addWidget(refresh_btn)
|
||||
layout.addLayout(refresh_row)
|
||||
self._setup_library_toolbar(layout)
|
||||
|
||||
self.ready_label = QLabel("Ready to Transfer:")
|
||||
layout.addWidget(self.ready_label)
|
||||
|
||||
self.library_ready_table = QTableWidget()
|
||||
self.library_ready_table.setColumnCount(7)
|
||||
self.library_ready_table.setHorizontalHeaderLabels(
|
||||
self.library_table = QTableWidget()
|
||||
self.library_table.setColumnCount(7)
|
||||
self.library_table.setHorizontalHeaderLabels(
|
||||
["#", "Artist", "Title", "Album", "Duration", "Genre", "Size"]
|
||||
)
|
||||
self.library_ready_table.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows)
|
||||
self.library_ready_table.setSelectionMode(QTableWidget.SelectionMode.ExtendedSelection)
|
||||
self.library_ready_table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers)
|
||||
self.library_ready_table.setAlternatingRowColors(True)
|
||||
self.library_ready_table.setSortingEnabled(True)
|
||||
self.library_ready_table.cellDoubleClicked.connect(self._on_table_double_clicked)
|
||||
self.library_table.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows)
|
||||
self.library_table.setSelectionMode(QTableWidget.SelectionMode.ExtendedSelection)
|
||||
self.library_table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers)
|
||||
self.library_table.setAlternatingRowColors(True)
|
||||
self.library_table.setSortingEnabled(True)
|
||||
self.library_table.cellDoubleClicked.connect(self._on_table_double_clicked)
|
||||
self.library_table.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
||||
self.library_table.customContextMenuRequested.connect(self._on_library_table_context_menu)
|
||||
|
||||
header = self.library_ready_table.horizontalHeader()
|
||||
header = self.library_table.horizontalHeader()
|
||||
header.setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch)
|
||||
header.setSectionResizeMode(2, QHeaderView.ResizeMode.Stretch)
|
||||
header.setSectionResizeMode(3, QHeaderView.ResizeMode.Stretch)
|
||||
@ -416,32 +414,7 @@ class MainWindow(QMainWindow):
|
||||
header.setSectionResizeMode(5, QHeaderView.ResizeMode.ResizeToContents)
|
||||
header.setSectionResizeMode(6, QHeaderView.ResizeMode.ResizeToContents)
|
||||
|
||||
layout.addWidget(self.library_ready_table, stretch=2)
|
||||
|
||||
self.source_label = QLabel("Source Files — Need Conversion:")
|
||||
layout.addWidget(self.source_label)
|
||||
|
||||
self.library_source_list = QListWidget()
|
||||
self.library_source_list.setSelectionMode(QListWidget.SelectionMode.ExtendedSelection)
|
||||
layout.addWidget(self.library_source_list, stretch=1)
|
||||
|
||||
btn_row = QHBoxLayout()
|
||||
self.library_add_btn = QPushButton("Add Files...")
|
||||
self.library_add_btn.clicked.connect(self._on_library_add_files)
|
||||
self.library_remove_btn = QPushButton("Remove Selected")
|
||||
self.library_remove_btn.clicked.connect(self._on_library_remove_selected)
|
||||
btn_row.addWidget(self.library_add_btn)
|
||||
btn_row.addWidget(self.library_remove_btn)
|
||||
btn_row.addStretch()
|
||||
layout.addLayout(btn_row)
|
||||
|
||||
self.library_convert_btn = QPushButton("Convert Selected")
|
||||
self.library_convert_btn.clicked.connect(self._on_library_convert_clicked)
|
||||
layout.addWidget(self.library_convert_btn)
|
||||
|
||||
self.library_transfer_btn = QPushButton("Transfer Selected to iPod \u25B8")
|
||||
self.library_transfer_btn.clicked.connect(self._on_library_transfer_clicked)
|
||||
layout.addWidget(self.library_transfer_btn)
|
||||
layout.addWidget(self.library_table, stretch=1)
|
||||
|
||||
self.library_progress = QProgressBar()
|
||||
self.library_progress.setRange(0, 100)
|
||||
@ -450,6 +423,44 @@ class MainWindow(QMainWindow):
|
||||
self.library_status = QLabel("Ready")
|
||||
layout.addWidget(self.library_status)
|
||||
|
||||
def _setup_library_toolbar(self, layout):
|
||||
toolbar = QHBoxLayout()
|
||||
toolbar.setSpacing(4)
|
||||
|
||||
self.library_add_btn = QPushButton("\u2795 Add Files...")
|
||||
self.library_add_btn.clicked.connect(self._on_library_add_files)
|
||||
toolbar.addWidget(self.library_add_btn)
|
||||
|
||||
self.library_convert_btn = QPushButton("\uD83D\uDD04 Convert")
|
||||
self.library_convert_btn.setEnabled(False)
|
||||
self.library_convert_btn.clicked.connect(self._on_library_convert_clicked)
|
||||
toolbar.addWidget(self.library_convert_btn)
|
||||
|
||||
self.library_transfer_btn = QPushButton("\uD83D\uDCE4 Transfer")
|
||||
self.library_transfer_btn.setEnabled(False)
|
||||
self.library_transfer_btn.clicked.connect(self._on_library_transfer_clicked)
|
||||
toolbar.addWidget(self.library_transfer_btn)
|
||||
|
||||
self.library_remove_btn = QPushButton("\uD83D\uDDD1\uFE0F Remove")
|
||||
self.library_remove_btn.setEnabled(False)
|
||||
self.library_remove_btn.clicked.connect(self._on_library_remove_selected)
|
||||
toolbar.addWidget(self.library_remove_btn)
|
||||
|
||||
refresh_btn = QPushButton("\uD83D\uDD04")
|
||||
refresh_btn.setToolTip("Refresh Library")
|
||||
refresh_btn.clicked.connect(self._on_refresh_library_clicked)
|
||||
toolbar.addWidget(refresh_btn)
|
||||
|
||||
toolbar.addStretch()
|
||||
|
||||
self.library_search_input = QLineEdit()
|
||||
self.library_search_input.setPlaceholderText("\uD83D\uDD0D Search Library...")
|
||||
self.library_search_input.setFixedWidth(220)
|
||||
self.library_search_input.textChanged.connect(self._on_search_text_changed)
|
||||
toolbar.addWidget(self.library_search_input)
|
||||
|
||||
layout.addLayout(toolbar)
|
||||
|
||||
def _setup_ipod_tab(self, tab):
|
||||
layout = QVBoxLayout(tab)
|
||||
|
||||
@ -565,6 +576,11 @@ class MainWindow(QMainWindow):
|
||||
self.auto_detect_check.setChecked(True)
|
||||
advanced_layout.addWidget(self.auto_detect_check)
|
||||
|
||||
self.show_download_check = QCheckBox("Show YouTube Download Tab")
|
||||
self.show_download_check.setChecked(False)
|
||||
self.show_download_check.toggled.connect(self._on_show_download_toggled)
|
||||
advanced_layout.addWidget(self.show_download_check)
|
||||
|
||||
layout.addWidget(advanced_group)
|
||||
|
||||
layout.addStretch()
|
||||
@ -577,6 +593,10 @@ class MainWindow(QMainWindow):
|
||||
about_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
layout.addWidget(about_label)
|
||||
|
||||
def _on_show_download_toggled(self, checked: bool):
|
||||
self.show_download_tab = checked
|
||||
self.tab_widget.setTabVisible(self.download_tab_index, checked)
|
||||
|
||||
def _setup_player(self):
|
||||
"""Initialize QMediaPlayer and QAudioOutput for playback."""
|
||||
self.player = QMediaPlayer()
|
||||
@ -590,12 +610,12 @@ class MainWindow(QMainWindow):
|
||||
|
||||
def _play_track_from_index(self, index: int):
|
||||
"""Start playing a track from the library table by row index."""
|
||||
if index < 0 or index >= self.library_ready_table.rowCount():
|
||||
if index < 0 or index >= self.library_table.rowCount():
|
||||
return
|
||||
data_item = self.library_ready_table.item(index, 0)
|
||||
data_item = self.library_table.item(index, 0)
|
||||
if data_item is None:
|
||||
return
|
||||
track_data = data_item.data(Qt.ItemDataRole.UserRole)
|
||||
is_ready, track_data = data_item.data(Qt.ItemDataRole.UserRole)
|
||||
if not track_data or not os.path.exists(track_data.get("path", "")):
|
||||
return
|
||||
|
||||
@ -611,8 +631,8 @@ class MainWindow(QMainWindow):
|
||||
)
|
||||
|
||||
# Highlight the current row
|
||||
self.library_ready_table.clearSelection()
|
||||
self.library_ready_table.selectRow(index)
|
||||
self.library_table.clearSelection()
|
||||
self.library_table.selectRow(index)
|
||||
|
||||
def _on_play_pause_clicked(self):
|
||||
if self.player is None:
|
||||
@ -624,7 +644,7 @@ class MainWindow(QMainWindow):
|
||||
else:
|
||||
if self.player.playbackState() == QMediaPlayer.PlaybackState.StoppedState:
|
||||
# Nothing playing yet — play first selected row or row 0
|
||||
selected_rows = {item.row() for item in self.library_ready_table.selectedItems()}
|
||||
selected_rows = {item.row() for item in self.library_table.selectedItems()}
|
||||
start = min(selected_rows) if selected_rows else 0
|
||||
self._play_track_from_index(start)
|
||||
else:
|
||||
@ -644,7 +664,7 @@ class MainWindow(QMainWindow):
|
||||
def _on_next_clicked(self):
|
||||
if self.player is None:
|
||||
return
|
||||
idx = min(self.library_ready_table.rowCount() - 1, self.current_playback_index + 1)
|
||||
idx = min(self.library_table.rowCount() - 1, self.current_playback_index + 1)
|
||||
self._play_track_from_index(idx)
|
||||
|
||||
def _on_slider_pressed(self):
|
||||
@ -705,8 +725,8 @@ class MainWindow(QMainWindow):
|
||||
output_dir = self.output_dir_input.text().strip()
|
||||
if not output_dir or not os.path.isdir(output_dir):
|
||||
self.library_ready.clear()
|
||||
self.library_scanned_sources = []
|
||||
self._refresh_library_ui()
|
||||
self._refresh_source_ui([])
|
||||
return
|
||||
|
||||
ready = []
|
||||
@ -778,68 +798,33 @@ class MainWindow(QMainWindow):
|
||||
ready = deduped_ready
|
||||
|
||||
self.library_ready = ready
|
||||
self.library_scanned_sources = sources
|
||||
self._refresh_library_ui()
|
||||
self._refresh_source_ui(sources)
|
||||
|
||||
def _refresh_library_ui(self):
|
||||
self.library_ready_table.setRowCount(0)
|
||||
self.library_ready_table.setSortingEnabled(False)
|
||||
|
||||
for track in sorted(
|
||||
self.library_ready,
|
||||
key=lambda t: (t.get("track_num") or 0, t["artist"].lower(), t["title"].lower())
|
||||
):
|
||||
row = self.library_ready_table.rowCount()
|
||||
self.library_ready_table.insertRow(row)
|
||||
|
||||
track_num_str = str(track["track_num"]) if track.get("track_num") else "—"
|
||||
|
||||
cells = [
|
||||
QTableWidgetItem(track_num_str),
|
||||
QTableWidgetItem(track["artist"]),
|
||||
QTableWidgetItem(track["title"]),
|
||||
QTableWidgetItem(track.get("album", "") or ""),
|
||||
QTableWidgetItem(self._format_duration(track["duration_s"])),
|
||||
QTableWidgetItem(track.get("genre", "") or ""),
|
||||
QTableWidgetItem(self._format_size(track["size"])),
|
||||
]
|
||||
|
||||
cells[0].setData(Qt.ItemDataRole.UserRole, track)
|
||||
for col, item in enumerate(cells):
|
||||
self.library_ready_table.setItem(row, col, item)
|
||||
|
||||
self.library_ready_table.setSortingEnabled(True)
|
||||
|
||||
count = len(self.library_ready)
|
||||
self.ready_label.setVisible(count > 0)
|
||||
self.library_ready_table.setVisible(count > 0)
|
||||
if count:
|
||||
self.library_status.setText(f"{count} track(s) ready to transfer")
|
||||
self.library_transfer_btn.setEnabled(True)
|
||||
else:
|
||||
self.library_status.setText("No tracks ready — convert source files or download")
|
||||
self.library_transfer_btn.setEnabled(False)
|
||||
|
||||
def _refresh_source_ui(self, scanned_sources):
|
||||
"""Build the Source Files list by combining scanned files + manual additions.
|
||||
|
||||
Deduplicates by path. All items get UserRole data for _get_selected_source_files().
|
||||
"""
|
||||
self.library_source_list.clear()
|
||||
self.library_table.setSortingEnabled(False)
|
||||
self.library_table.setRowCount(0)
|
||||
|
||||
seen_paths = set()
|
||||
all_sources = []
|
||||
all_tracks = []
|
||||
|
||||
for src in scanned_sources:
|
||||
path = src.get("path", "")
|
||||
# Add ready tracks
|
||||
for t in self.library_ready:
|
||||
path = t.get("path", "")
|
||||
if path and path not in seen_paths and os.path.exists(path):
|
||||
seen_paths.add(path)
|
||||
all_sources.append(src)
|
||||
all_tracks.append({**t, "_status": "ready"})
|
||||
|
||||
# Add scanned source files
|
||||
for s in self.library_scanned_sources:
|
||||
path = s.get("path", "")
|
||||
if path and path not in seen_paths and os.path.exists(path):
|
||||
seen_paths.add(path)
|
||||
all_tracks.append({**s, "duration_s": 0, "genre": "", "_status": "source"})
|
||||
|
||||
# Add manually added source files
|
||||
for fpath in self.library_source_files:
|
||||
if fpath in seen_paths:
|
||||
continue
|
||||
if not os.path.exists(fpath):
|
||||
if fpath in seen_paths or not os.path.exists(fpath):
|
||||
continue
|
||||
fname = os.path.basename(fpath)
|
||||
size = os.path.getsize(fpath)
|
||||
@ -851,65 +836,115 @@ class MainWindow(QMainWindow):
|
||||
except Exception:
|
||||
title = fname
|
||||
artist = "Unknown"
|
||||
|
||||
src = {
|
||||
"path": fpath,
|
||||
"title": title,
|
||||
"artist": artist,
|
||||
"size": size,
|
||||
}
|
||||
all_sources.append(src)
|
||||
seen_paths.add(fpath)
|
||||
all_tracks.append({
|
||||
"path": fpath, "title": title, "artist": artist,
|
||||
"size": size, "duration_s": 0, "genre": "", "album": "",
|
||||
"_status": "source",
|
||||
})
|
||||
|
||||
for src in sorted(all_sources, key=lambda s: (s["artist"], s["title"])):
|
||||
display = f"{src['artist']} — {src['title']} {self._format_size(src['size'])}"
|
||||
item = QListWidgetItem(display)
|
||||
item.setData(Qt.ItemDataRole.UserRole, src)
|
||||
self.library_source_list.addItem(item)
|
||||
all_tracks.sort(key=lambda t: (t["artist"].lower(), t["title"].lower()))
|
||||
|
||||
count = len(all_sources)
|
||||
self.source_label.setVisible(count > 0)
|
||||
self.library_source_list.setVisible(count > 0)
|
||||
self.library_convert_btn.setEnabled(count > 0)
|
||||
for idx, track in enumerate(all_tracks):
|
||||
row = self.library_table.rowCount()
|
||||
self.library_table.insertRow(row)
|
||||
|
||||
is_ready = track["_status"] == "ready"
|
||||
|
||||
track_num_str = str(track.get("track_num") or "") if is_ready else ""
|
||||
duration_str = self._format_duration(track.get("duration_s", 0)) if is_ready else "\u2014"
|
||||
|
||||
cells = [
|
||||
QTableWidgetItem(track_num_str),
|
||||
QTableWidgetItem(track["artist"]),
|
||||
QTableWidgetItem(track["title"]),
|
||||
QTableWidgetItem(track.get("album", "") or ""),
|
||||
QTableWidgetItem(duration_str),
|
||||
QTableWidgetItem(track.get("genre", "") or ""),
|
||||
QTableWidgetItem(self._format_size(track["size"])),
|
||||
]
|
||||
|
||||
# Dim source tracks (gray text) like iTunes
|
||||
if not is_ready:
|
||||
gray = self.palette().color(self.palette().ColorRole.PlaceholderText)
|
||||
for item in cells:
|
||||
item.setForeground(gray)
|
||||
|
||||
# Store full track data in column 0
|
||||
data_key = track.copy()
|
||||
data_key.pop("_status", None)
|
||||
cells[0].setData(Qt.ItemDataRole.UserRole, (is_ready, data_key))
|
||||
|
||||
for col, item in enumerate(cells):
|
||||
self.library_table.setItem(row, col, item)
|
||||
|
||||
self.library_table.setSortingEnabled(True)
|
||||
|
||||
ready_count = sum(1 for t in all_tracks if t["_status"] == "ready")
|
||||
source_count = len(all_tracks) - ready_count
|
||||
|
||||
has_ready = ready_count > 0
|
||||
has_source = source_count > 0
|
||||
|
||||
self.library_transfer_btn.setEnabled(has_ready)
|
||||
self.library_convert_btn.setEnabled(has_source)
|
||||
self.library_remove_btn.setEnabled(len(all_tracks) > 0)
|
||||
|
||||
parts = []
|
||||
if ready_count:
|
||||
parts.append(f"{ready_count} ready")
|
||||
if source_count:
|
||||
parts.append(f"{source_count} need conversion")
|
||||
status = f"{len(all_tracks)} track(s) \u2014 {', '.join(parts)}" if parts else "No tracks in library"
|
||||
self.library_status.setText(status)
|
||||
|
||||
def _get_selected_ready_tracks(self) -> List[Tuple[TrackInfo, str]]:
|
||||
selected_rows = set()
|
||||
for item in self.library_ready_table.selectedItems():
|
||||
for item in self.library_table.selectedItems():
|
||||
selected_rows.add(item.row())
|
||||
|
||||
if not selected_rows:
|
||||
selected_rows = set(range(self.library_ready_table.rowCount()))
|
||||
selected_rows = set(range(self.library_table.rowCount()))
|
||||
|
||||
tracks = []
|
||||
for row in sorted(selected_rows):
|
||||
data_item = self.library_ready_table.item(row, 0)
|
||||
data_item = self.library_table.item(row, 0)
|
||||
if data_item is None:
|
||||
continue
|
||||
data = data_item.data(Qt.ItemDataRole.UserRole)
|
||||
if data:
|
||||
track_info = TrackInfo(
|
||||
video_id=f"local_{hash(data['path'])}",
|
||||
title=data['title'],
|
||||
artist=data['artist'],
|
||||
album=data.get("album", ""),
|
||||
thumbnail_url="",
|
||||
duration=data.get('duration_s', 0),
|
||||
track_number=data.get('track_num'),
|
||||
genre=data.get('genre'),
|
||||
download_path=data['path'],
|
||||
)
|
||||
tracks.append((track_info, data['path']))
|
||||
is_ready, data = data_item.data(Qt.ItemDataRole.UserRole)
|
||||
if not is_ready:
|
||||
continue
|
||||
track_info = TrackInfo(
|
||||
video_id=f"local_{hash(data['path'])}",
|
||||
title=data['title'],
|
||||
artist=data['artist'],
|
||||
album=data.get("album", ""),
|
||||
thumbnail_url="",
|
||||
duration=data.get('duration_s', 0),
|
||||
track_number=data.get('track_num'),
|
||||
genre=data.get('genre'),
|
||||
download_path=data['path'],
|
||||
)
|
||||
tracks.append((track_info, data['path']))
|
||||
return tracks
|
||||
|
||||
def _get_selected_source_files(self) -> List[str]:
|
||||
selected = self.library_source_list.selectedItems()
|
||||
if not selected:
|
||||
selected = [self.library_source_list.item(i) for i in range(self.library_source_list.count())]
|
||||
selected_rows = set()
|
||||
for item in self.library_table.selectedItems():
|
||||
selected_rows.add(item.row())
|
||||
|
||||
if not selected_rows:
|
||||
selected_rows = set(range(self.library_table.rowCount()))
|
||||
|
||||
paths = []
|
||||
for item in selected:
|
||||
data = item.data(Qt.ItemDataRole.UserRole)
|
||||
if data and os.path.exists(data.get("path", "")):
|
||||
for row in sorted(selected_rows):
|
||||
data_item = self.library_table.item(row, 0)
|
||||
if data_item is None:
|
||||
continue
|
||||
is_ready, data = data_item.data(Qt.ItemDataRole.UserRole)
|
||||
if is_ready:
|
||||
continue
|
||||
if os.path.exists(data.get("path", "")):
|
||||
paths.append(data["path"])
|
||||
return paths
|
||||
|
||||
@ -938,26 +973,21 @@ class MainWindow(QMainWindow):
|
||||
new_files.append(f)
|
||||
|
||||
if new_files:
|
||||
self._refresh_source_ui([])
|
||||
self._scan_library()
|
||||
|
||||
def _on_library_remove_selected(self):
|
||||
"""Delete selected files from disk and clean up empty directories."""
|
||||
files_to_delete = []
|
||||
|
||||
selected_rows = set()
|
||||
for item in self.library_ready_table.selectedItems():
|
||||
for item in self.library_table.selectedItems():
|
||||
selected_rows.add(item.row())
|
||||
|
||||
for row in selected_rows:
|
||||
data_item = self.library_ready_table.item(row, 0)
|
||||
data_item = self.library_table.item(row, 0)
|
||||
if data_item is None:
|
||||
continue
|
||||
data = data_item.data(Qt.ItemDataRole.UserRole)
|
||||
if data and os.path.exists(data.get("path", "")):
|
||||
files_to_delete.append(data)
|
||||
|
||||
for item in self.library_source_list.selectedItems():
|
||||
data = item.data(Qt.ItemDataRole.UserRole)
|
||||
is_ready, data = data_item.data(Qt.ItemDataRole.UserRole)
|
||||
if data and os.path.exists(data.get("path", "")):
|
||||
files_to_delete.append(data)
|
||||
|
||||
@ -1002,6 +1032,57 @@ class MainWindow(QMainWindow):
|
||||
self._scan_library()
|
||||
QMessageBox.information(self, "Done", f"Deleted {deleted} file(s)")
|
||||
|
||||
def _on_search_text_changed(self, text: str):
|
||||
for row in range(self.library_table.rowCount()):
|
||||
item = self.library_table.item(row, 3)
|
||||
match = not text or text.lower() in item.text().lower() if item else True
|
||||
self.library_table.setRowHidden(row, not match)
|
||||
|
||||
def _on_library_table_context_menu(self, pos):
|
||||
item = self.library_table.itemAt(pos)
|
||||
if item is None:
|
||||
return
|
||||
row = item.row()
|
||||
data_item = self.library_table.item(row, 0)
|
||||
if data_item is None:
|
||||
return
|
||||
is_ready, track_data = data_item.data(Qt.ItemDataRole.UserRole)
|
||||
|
||||
menu = QMenu(self)
|
||||
|
||||
play_action = menu.addAction("\u25B6 Play")
|
||||
play_action.triggered.connect(lambda: self._play_track_from_index(row))
|
||||
|
||||
if is_ready:
|
||||
transfer_action = menu.addAction("\uD83D\uDCE4 Transfer to iPod")
|
||||
transfer_action.triggered.connect(self._on_library_transfer_clicked)
|
||||
else:
|
||||
convert_action = menu.addAction("\uD83D\uDD04 Convert to iPod Format")
|
||||
convert_action.triggered.connect(self._on_library_convert_clicked)
|
||||
|
||||
menu.addSeparator()
|
||||
|
||||
show_action = menu.addAction("\uD83D\uDCC2 Show in File Manager")
|
||||
show_action.triggered.connect(lambda: self._on_show_in_file_manager(track_data))
|
||||
|
||||
remove_action = menu.addAction("\uD83D\uDDD1\uFE0F Remove from Library")
|
||||
remove_action.triggered.connect(self._on_library_remove_selected)
|
||||
|
||||
menu.exec(self.library_table.viewport().mapToGlobal(pos))
|
||||
|
||||
def _on_show_in_file_manager(self, track_data):
|
||||
path = track_data.get("path", "")
|
||||
if not path:
|
||||
return
|
||||
import subprocess
|
||||
platform = sys.platform
|
||||
if platform == "win32":
|
||||
subprocess.Popen(["explorer", "/select,", os.path.normpath(path)])
|
||||
elif platform == "darwin":
|
||||
subprocess.Popen(["open", "-R", path])
|
||||
else:
|
||||
subprocess.Popen(["xdg-open", os.path.dirname(path)])
|
||||
|
||||
def _on_download_clicked(self):
|
||||
url = self.url_input.text().strip()
|
||||
if not url:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user