Videre
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -49,6 +49,11 @@ class LibraryManagerDialog(QDialog):
|
||||
btn_remove.clicked.connect(self._remove_selected)
|
||||
btn_row.addWidget(btn_remove)
|
||||
|
||||
btn_scan = QPushButton("⟳ Scan alle")
|
||||
btn_scan.setToolTip("Scan alle mapper for nye og ændrede filer")
|
||||
btn_scan.clicked.connect(self._scan_all)
|
||||
btn_row.addWidget(btn_scan)
|
||||
|
||||
btn_row.addStretch()
|
||||
btn_close = QPushButton("Luk")
|
||||
btn_close.clicked.connect(self.accept)
|
||||
@@ -83,6 +88,15 @@ class LibraryManagerDialog(QDialog):
|
||||
except Exception as e:
|
||||
print(f"Library manager load fejl: {e}")
|
||||
|
||||
def _scan_all(self):
|
||||
mw = self.parent()
|
||||
if hasattr(mw, "start_scan"):
|
||||
mw.start_scan()
|
||||
self._set_status("Scanning startet...")
|
||||
|
||||
def _set_status(self, text: str):
|
||||
pass # kan udvides med statuslinje i dialogen
|
||||
|
||||
def _add_folder(self):
|
||||
from PyQt6.QtWidgets import QFileDialog
|
||||
folder = QFileDialog.getExistingDirectory(self, "Vælg musikmappe")
|
||||
@@ -90,7 +104,9 @@ class LibraryManagerDialog(QDialog):
|
||||
mw = self.parent()
|
||||
if hasattr(mw, "add_library_path"):
|
||||
mw.add_library_path(folder)
|
||||
self._load()
|
||||
# Genindlæs listen efter kort pause så DB er opdateret
|
||||
from PyQt6.QtCore import QTimer
|
||||
QTimer.singleShot(600, self._load)
|
||||
|
||||
def _remove_selected(self):
|
||||
item = self._list.currentItem()
|
||||
|
||||
@@ -70,22 +70,11 @@ class LibraryPanel(QWidget):
|
||||
header.addWidget(lbl)
|
||||
header.addStretch()
|
||||
|
||||
self._btn_scan = QPushButton("⟳ SCAN")
|
||||
self._btn_scan.setFixedHeight(24)
|
||||
self._btn_scan.setToolTip("Scan alle biblioteksmapper for nye og ændrede filer")
|
||||
self._btn_scan.clicked.connect(self._on_scan_clicked)
|
||||
header.addWidget(self._btn_scan)
|
||||
|
||||
btn_manage = QPushButton("⚙ Mapper")
|
||||
btn_manage.setFixedHeight(24)
|
||||
btn_manage.setToolTip("Tilføj eller fjern musikbiblioteker")
|
||||
btn_manage.setToolTip("Tilføj, fjern og scan musikbiblioteker")
|
||||
btn_manage.clicked.connect(self._manage_libraries)
|
||||
header.addWidget(btn_manage)
|
||||
|
||||
btn_add = QPushButton("+ MAPPE")
|
||||
btn_add.setFixedHeight(24)
|
||||
btn_add.clicked.connect(self._add_folder)
|
||||
header.addWidget(btn_add)
|
||||
layout.addLayout(header)
|
||||
|
||||
# Scan status
|
||||
@@ -136,14 +125,10 @@ class LibraryPanel(QWidget):
|
||||
|
||||
def set_scanning(self, scanning: bool, status_text: str = ""):
|
||||
if scanning:
|
||||
self._btn_scan.setEnabled(False)
|
||||
self._btn_scan.setText("⟳ SCANNER...")
|
||||
self._scan_bar.show()
|
||||
self._scan_label.setText(status_text or "Starter...")
|
||||
self._scan_label.show()
|
||||
else:
|
||||
self._btn_scan.setEnabled(True)
|
||||
self._btn_scan.setText("⟳ SCAN")
|
||||
self._scan_bar.hide()
|
||||
self._scan_label.hide()
|
||||
|
||||
@@ -216,6 +201,7 @@ class LibraryPanel(QWidget):
|
||||
act_play = menu.addAction("Afspil")
|
||||
menu.addSeparator()
|
||||
act_tags = menu.addAction("✎ Rediger dans-tags...")
|
||||
act_bpm = menu.addAction("♩ Analysér BPM")
|
||||
menu.addSeparator()
|
||||
send_menu = menu.addMenu("Send til")
|
||||
act_mail = send_menu.addAction("✉ Send som mail")
|
||||
@@ -226,9 +212,39 @@ class LibraryPanel(QWidget):
|
||||
self.song_selected.emit(song)
|
||||
elif action == act_tags:
|
||||
self.edit_tags_requested.emit(song)
|
||||
elif action == act_bpm:
|
||||
self._analyze_bpm(song)
|
||||
elif action == act_mail:
|
||||
self.send_mail_requested.emit(song)
|
||||
|
||||
def _analyze_bpm(self, song: dict):
|
||||
"""Analysér BPM i baggrundstråd og opdater biblioteket."""
|
||||
path = song.get("local_path", "")
|
||||
song_id = song.get("id", "")
|
||||
if not path or not song_id:
|
||||
return
|
||||
from PyQt6.QtCore import QThread, pyqtSignal as _sig
|
||||
|
||||
class BpmWorker(QThread):
|
||||
done = _sig(float)
|
||||
def __init__(self, p, sid):
|
||||
super().__init__()
|
||||
self._p, self._sid = p, sid
|
||||
def run(self):
|
||||
from local.tag_reader import analyze_and_save_bpm
|
||||
bpm = analyze_and_save_bpm(self._p, self._sid)
|
||||
if bpm:
|
||||
self.done.emit(bpm)
|
||||
|
||||
self._bpm_worker = BpmWorker(path, song_id)
|
||||
self._bpm_worker.done.connect(
|
||||
lambda bpm: (
|
||||
self._do_search(),
|
||||
print(f"BPM analyseret: {bpm}")
|
||||
)
|
||||
)
|
||||
self._bpm_worker.start()
|
||||
|
||||
def _manage_libraries(self):
|
||||
from ui.library_manager import LibraryManagerDialog
|
||||
dialog = LibraryManagerDialog(parent=self.window())
|
||||
|
||||
@@ -115,38 +115,25 @@ class MainWindow(QMainWindow):
|
||||
def _build_menu(self):
|
||||
menubar = self.menuBar()
|
||||
|
||||
# Filer
|
||||
# ── Filer ─────────────────────────────────────────────────────────────
|
||||
file_menu = menubar.addMenu("Filer")
|
||||
|
||||
file_menu.addSeparator()
|
||||
|
||||
self._act_go_online = QAction("Gå online...", self)
|
||||
self._act_go_online.setShortcut("Ctrl+L")
|
||||
self._act_go_online.setToolTip("Log ind og synkroniser med server")
|
||||
self._act_go_online.triggered.connect(self._go_online)
|
||||
file_menu.addAction(self._act_go_online)
|
||||
|
||||
self._act_go_offline = QAction("Gå offline", self)
|
||||
self._act_go_offline.setToolTip("Log ud og arbejd lokalt")
|
||||
self._act_go_offline.triggered.connect(self._go_offline)
|
||||
self._act_go_offline.setEnabled(False) # kun aktiv når man er online
|
||||
self._act_go_offline.setEnabled(False)
|
||||
file_menu.addAction(self._act_go_offline)
|
||||
|
||||
file_menu.addSeparator()
|
||||
|
||||
act_add_folder = QAction("Tilføj musikmappe...", self)
|
||||
act_add_folder.setShortcut("Ctrl+O")
|
||||
act_add_folder.triggered.connect(self._menu_add_folder)
|
||||
file_menu.addAction(act_add_folder)
|
||||
|
||||
file_menu.addSeparator()
|
||||
|
||||
act_scan = QAction("Scan biblioteker", self)
|
||||
act_scan.setShortcut("Ctrl+R")
|
||||
act_scan.setToolTip("Gennemgå alle biblioteksmapper for nye og ændrede filer")
|
||||
act_scan.triggered.connect(self.start_scan)
|
||||
file_menu.addAction(act_scan)
|
||||
self._act_scan = act_scan
|
||||
act_settings = QAction("Indstillinger...", self)
|
||||
act_settings.setShortcut("Ctrl+,")
|
||||
act_settings.triggered.connect(self._open_settings)
|
||||
file_menu.addAction(act_settings)
|
||||
|
||||
file_menu.addSeparator()
|
||||
|
||||
@@ -155,33 +142,14 @@ class MainWindow(QMainWindow):
|
||||
act_quit.triggered.connect(self.close)
|
||||
file_menu.addAction(act_quit)
|
||||
|
||||
# Danseliste
|
||||
pl_menu = menubar.addMenu("Danseliste")
|
||||
# ── Ingen Danseliste- eller Visning-menu ──────────────────────────────
|
||||
# Ny/Gem/Hent ligger direkte i danseliste-panelet
|
||||
# Tema-skift ligger i topbar-knappen
|
||||
# Mapper og scan ligger i ⚙ Mapper dialogen
|
||||
|
||||
act_new_pl = QAction("Ny tom liste", self)
|
||||
act_new_pl.setShortcut("Ctrl+N")
|
||||
act_new_pl.triggered.connect(self._new_playlist)
|
||||
pl_menu.addAction(act_new_pl)
|
||||
|
||||
act_manage = QAction("Gem / Indlæs / Importer...", self)
|
||||
act_manage.setShortcut("Ctrl+M")
|
||||
act_manage.triggered.connect(self._open_playlist_manager)
|
||||
pl_menu.addAction(act_manage)
|
||||
|
||||
# Visning
|
||||
view_menu = menubar.addMenu("Visning")
|
||||
|
||||
act_theme = QAction("Skift tema (lyst/mørkt)", self)
|
||||
act_theme.setShortcut("Ctrl+T")
|
||||
act_theme.triggered.connect(self._toggle_theme)
|
||||
view_menu.addAction(act_theme)
|
||||
|
||||
view_menu.addSeparator()
|
||||
|
||||
act_settings = QAction("Indstillinger...", self)
|
||||
act_settings.setShortcut("Ctrl+,")
|
||||
act_settings.triggered.connect(self._open_settings)
|
||||
view_menu.addAction(act_settings)
|
||||
# Gem reference til scan-action (bruges stadig internt)
|
||||
self._act_scan = QAction("Scan", self)
|
||||
self._act_scan.triggered.connect(self.start_scan)
|
||||
|
||||
# ── Statuslinje ───────────────────────────────────────────────────────────
|
||||
|
||||
@@ -399,8 +367,10 @@ class MainWindow(QMainWindow):
|
||||
|
||||
init_db()
|
||||
|
||||
# Brug et Qt signal til thread-safe reload fra watcher-tråden
|
||||
from PyQt6.QtCore import QMetaObject, Q_ARG
|
||||
def on_file_change(event_type, path, song_id):
|
||||
QTimer.singleShot(500, self._reload_library)
|
||||
QTimer.singleShot(0, self._reload_library)
|
||||
|
||||
self._watcher = get_watcher(on_change=on_file_change)
|
||||
self._watcher.start()
|
||||
@@ -494,10 +464,16 @@ class MainWindow(QMainWindow):
|
||||
|
||||
def add_library_path(self, path: str):
|
||||
try:
|
||||
if not self._watcher:
|
||||
self._set_status("Watcher ikke klar endnu — prøv igen om et øjeblik", 3000)
|
||||
return
|
||||
self._watcher.add_library(path)
|
||||
self._set_status(f"Tilføjet: {path} — scanner...")
|
||||
# Genindlæs bibliotekslisten og start scan
|
||||
QTimer.singleShot(500, self._reload_library)
|
||||
QTimer.singleShot(1000, self.start_scan)
|
||||
except Exception as e:
|
||||
self._set_status(f"Fejl: {e}")
|
||||
self._set_status(f"Fejl ved tilføjelse: {e}")
|
||||
|
||||
def _open_settings(self):
|
||||
dialog = SettingsDialog(parent=self)
|
||||
@@ -860,10 +836,15 @@ class MainWindow(QMainWindow):
|
||||
self._vu.reset()
|
||||
|
||||
# Markér den afspillede sang
|
||||
self._playlist_panel.mark_played(self._current_idx)
|
||||
prev_idx = self._current_idx
|
||||
self._playlist_panel.mark_played(prev_idx)
|
||||
|
||||
# Find næste afspilbare sang — spring skippede og afspillede over
|
||||
ni = self._playlist_panel.next_playable_idx(self._current_idx + 1)
|
||||
# Synkroniser event-status til den gemte navngivne liste
|
||||
self._sync_event_status_to_playlist()
|
||||
|
||||
# Find næste afspilbare sang — fra 0 hvis ingen sang var i gang
|
||||
search_from = max(0, prev_idx + 1)
|
||||
ni = self._playlist_panel.next_playable_idx(search_from)
|
||||
next_song = self._playlist_panel.get_song(ni) if ni is not None else None
|
||||
if next_song:
|
||||
self._current_idx = ni
|
||||
@@ -876,6 +857,29 @@ class MainWindow(QMainWindow):
|
||||
self._lbl_dances.setText("")
|
||||
self._set_status("Danselisten er afsluttet")
|
||||
|
||||
def _sync_event_status_to_playlist(self):
|
||||
"""Gem event-fremgang i den aktive navngivne liste."""
|
||||
try:
|
||||
from local.local_db import get_db
|
||||
songs = self._playlist_panel.get_songs()
|
||||
statuses = self._playlist_panel.get_statuses()
|
||||
with get_db() as conn:
|
||||
# Find den aktive liste (ikke __aktiv__)
|
||||
pl = conn.execute(
|
||||
"SELECT id FROM playlists WHERE name != '__aktiv__' "
|
||||
"ORDER BY created_at DESC LIMIT 1"
|
||||
).fetchone()
|
||||
if not pl:
|
||||
return
|
||||
# Opdater status for hver sang i listen
|
||||
for i, (song, status) in enumerate(zip(songs, statuses)):
|
||||
conn.execute("""
|
||||
UPDATE playlist_songs SET status=?
|
||||
WHERE playlist_id=? AND song_id=?
|
||||
""", (status, pl["id"], song.get("id")))
|
||||
except Exception as e:
|
||||
print(f"Event-status sync fejl: {e}")
|
||||
|
||||
def _on_state_changed(self, state: str):
|
||||
if state == "playing":
|
||||
self._btn_play.setText("⏸")
|
||||
|
||||
@@ -216,6 +216,28 @@ QScrollBar::handle:vertical {
|
||||
}
|
||||
QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { height: 0; }
|
||||
|
||||
/* Højreklik-menu */
|
||||
QMenu {
|
||||
background-color: #22252a;
|
||||
color: #e8eaf0;
|
||||
border: 1px solid #4a5060;
|
||||
padding: 4px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
QMenu::item {
|
||||
padding: 8px 24px;
|
||||
border-radius: 0;
|
||||
}
|
||||
QMenu::item:selected {
|
||||
background-color: #e8a020;
|
||||
color: #111214;
|
||||
}
|
||||
QMenu::separator {
|
||||
height: 1px;
|
||||
background: #3a3e46;
|
||||
margin: 4px 8px;
|
||||
}
|
||||
|
||||
/* Topbar */
|
||||
QFrame#topbar {
|
||||
background-color: #1a1c1f;
|
||||
@@ -247,7 +269,7 @@ QMainWindow, #root {
|
||||
}
|
||||
QPushButton {
|
||||
background-color: #b0b4bc;
|
||||
color: #4a5060;
|
||||
color: #1a1c22;
|
||||
border-color: #8890a0;
|
||||
}
|
||||
QPushButton:hover {
|
||||
@@ -262,10 +284,19 @@ QPushButton#btn_play {
|
||||
}
|
||||
QListWidget {
|
||||
background-color: #d8dae0;
|
||||
color: #1a1c22;
|
||||
}
|
||||
QListWidget::item {
|
||||
color: #1a1c22;
|
||||
}
|
||||
QListWidget::item:selected {
|
||||
background-color: #eef0f4;
|
||||
border-left: 2px solid #c07010;
|
||||
background-color: #c07010;
|
||||
color: #ffffff;
|
||||
border-left: 2px solid #a05808;
|
||||
}
|
||||
QListWidget::item:hover {
|
||||
background-color: #c8ccd4;
|
||||
color: #1a1c22;
|
||||
}
|
||||
QLineEdit {
|
||||
background-color: #c8cad0;
|
||||
@@ -280,12 +311,22 @@ QFrame#transport_frame, QFrame#progress_frame {
|
||||
}
|
||||
QFrame#track_display { background-color: #c8cad0; border-color: #aab0bc; }
|
||||
QFrame#topbar { background-color: #d8dae0; border-color: #aab0bc; }
|
||||
QLabel#section_title { background-color: #e4e6ec; color: #8890a0; border-color: #aab0bc; }
|
||||
QLabel#section_title { background-color: #e4e6ec; color: #1a1c22; border-color: #aab0bc; }
|
||||
QLabel#track_title { color: #1a1c22; }
|
||||
QLabel#track_meta { color: #4a5060; }
|
||||
QLabel#result_count { color: #5a6070; }
|
||||
QSlider::groove:horizontal { background: #b0b4bc; }
|
||||
QScrollBar:vertical { background: #d8dae0; }
|
||||
QScrollBar::handle:vertical { background: #8890a0; }
|
||||
QMenu {
|
||||
background-color: #e4e6ec;
|
||||
color: #1a1c22;
|
||||
border: 1px solid #aab0bc;
|
||||
}
|
||||
QMenu::item:selected {
|
||||
background-color: #c07010;
|
||||
color: #ffffff;
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user