From 45dcedaed4a621ea1a58be5ef1305bf0adf64b8b Mon Sep 17 00:00:00 2001 From: Carsten Kvist Date: Sun, 12 Apr 2026 19:19:01 +0200 Subject: [PATCH] Windows --- linedance-app/build_windows.spec | 29 +++++++++++----------- linedance-app/ui/library_panel.py | 41 ++++++++++++++++--------------- linedance-app/ui/main_window.py | 25 +++++++++++-------- linedance-app/ui/tag_editor.py | 30 ++++++++++++++++++---- 4 files changed, 75 insertions(+), 50 deletions(-) diff --git a/linedance-app/build_windows.spec b/linedance-app/build_windows.spec index f22fcccb..d28a87c5 100644 --- a/linedance-app/build_windows.spec +++ b/linedance-app/build_windows.spec @@ -104,26 +104,25 @@ pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) exe = EXE( pyz, a.scripts, - a.binaries, - a.zipfiles, - a.datas, - [], + [], # ← onedir: ingen binaries/datas her + exclude_binaries=True, # ← onedir: binaries samles i COLLECT name='LineDancePlayer', debug=False, bootloader_ignore_signals=False, strip=False, upx=True, - upx_exclude=[ - 'vcruntime140.dll', - 'msvcp140.dll', - 'python3*.dll', - 'Qt6*.dll', - ], + upx_exclude=['Qt6*.dll', 'python3*.dll', 'vcruntime140.dll'], console=False, - disable_windowed_traceback=False, - target_arch=None, - codesign_identity=None, - entitlements_file=None, icon=None, - onefile=True, +) + +coll = COLLECT( + exe, + a.binaries, + a.zipfiles, + a.datas, + strip=False, + upx=True, + upx_exclude=['Qt6*.dll', 'python3*.dll', 'vcruntime140.dll'], + name='LineDancePlayer', ) diff --git a/linedance-app/ui/library_panel.py b/linedance-app/ui/library_panel.py index 4766d2e7..0e44dbc1 100644 --- a/linedance-app/ui/library_panel.py +++ b/linedance-app/ui/library_panel.py @@ -138,11 +138,20 @@ class LibraryPanel(QWidget): header.addWidget(btn_manage) layout.addLayout(header) - # Søgefelt + # Søgefelt + checkbox + search_row = QHBoxLayout() + search_row.setSpacing(6) self._search = QLineEdit() self._search.setPlaceholderText("Søg i titel, artist, album, dans...") self._search.textChanged.connect(self._on_search_changed) - layout.addWidget(self._search) + search_row.addWidget(self._search) + from PyQt6.QtWidgets import QCheckBox + self._chk_alt = QCheckBox("Inkl. alt.") + self._chk_alt.setToolTip("Søg også i alternativ-danse") + self._chk_alt.setChecked(False) + self._chk_alt.toggled.connect(self._on_search_changed) + search_row.addWidget(self._chk_alt) + layout.addLayout(search_row) # Resultat-tæller + drag-hint hint_row = QHBoxLayout() @@ -190,7 +199,10 @@ class LibraryPanel(QWidget): def _do_search(self): q = self._search.text().strip().lower() - self._filtered = [s for s in self._all_songs if self._matches(s, q)] if q else list(self._all_songs) + incl_alt = self._chk_alt.isChecked() + self._filtered = [ + s for s in self._all_songs if self._matches(s, q, incl_alt) + ] if q else list(self._all_songs) total = len(self._all_songs) found = len(self._filtered) q_text = self._search.text().strip() @@ -200,25 +212,14 @@ class LibraryPanel(QWidget): ) self._render() - def _matches(self, song: dict, q: str) -> bool: - return any(q in f.lower() for f in [ + def _matches(self, song: dict, q: str, incl_alt: bool = False) -> bool: + fields = [ song.get("title", ""), song.get("artist", ""), song.get("album", ""), song.get("file_format", ""), - ] + song.get("dances", [])) - - def _render(self): - self._list.clear() - q = self._search.text().strip().lower() - for song in self._filtered: - dances = song.get("dances", []) - dance_levels = song.get("dance_levels", []) - missing = song.get("file_missing", False) - - dance_parts = [] - for i, d in enumerate(dances): - lvl = dance_levels[i] if i < len(dance_levels) else "" - dance_parts.append(f"{d} / {lvl}" if lvl else d) - dance_str = " · " + " | ".join(dance_parts) if dance_parts else "" + ] + song.get("dances", []) + if incl_alt: + fields += song.get("alt_dances", []) + return any(q in f.lower() for f in fields) def _render(self): self._list.clear() diff --git a/linedance-app/ui/main_window.py b/linedance-app/ui/main_window.py index c1ac381b..76b4274f 100644 --- a/linedance-app/ui/main_window.py +++ b/linedance-app/ui/main_window.py @@ -287,10 +287,10 @@ class MainWindow(QMainWindow): b.setCheckable(True) return b - self._btn_prev = btn("⏮", size=52) + self._btn_prev = btn("|◀◀", size=52) self._btn_play = btn("▶", "btn_play", size=72) - self._btn_stop = btn("⏹", "btn_stop", size=52) - self._btn_next = btn("⏭", size=52) + self._btn_stop = btn("■", "btn_stop", size=52) + self._btn_next = btn("▶▶|", size=52) self._btn_demo = btn(f"▶\n{self._demo_seconds} SEK", "btn_demo", size=64, checkable=True) self._btn_prev.clicked.connect(self._prev_song) @@ -453,12 +453,15 @@ class MainWindow(QMainWindow): SELECT s.id, s.title, s.artist, s.album, s.bpm, s.duration_sec, s.local_path, s.file_format, s.file_missing, - GROUP_CONCAT(d.name, '||') AS dance_names, - GROUP_CONCAT(COALESCE(dl.name,''), '||') AS dance_levels + GROUP_CONCAT(DISTINCT d.name) AS dance_names, + GROUP_CONCAT(DISTINCT COALESCE(dl.name,'')) AS dance_levels, + GROUP_CONCAT(DISTINCT ad.name) AS alt_dance_names FROM songs s - LEFT JOIN song_dances sd ON sd.song_id = s.id - LEFT JOIN dances d ON d.id = sd.dance_id - LEFT JOIN dance_levels dl ON dl.id = d.level_id + LEFT JOIN song_dances sd ON sd.song_id = s.id + LEFT JOIN dances d ON d.id = sd.dance_id + LEFT JOIN dance_levels dl ON dl.id = d.level_id + LEFT JOIN song_alt_dances sad ON sad.song_id = s.id + LEFT JOIN dances ad ON ad.id = sad.dance_id WHERE s.file_missing = 0 GROUP BY s.id ORDER BY s.artist, s.title @@ -467,8 +470,9 @@ class MainWindow(QMainWindow): songs = [] for row in rows: - dances = row["dance_names"].split("||") if row["dance_names"] else [] - levels = row["dance_levels"].split("||") if row["dance_levels"] else [] + dances = row["dance_names"].split(",") if row["dance_names"] else [] + levels = row["dance_levels"].split(",") if row["dance_levels"] else [] + alt_dances = row["alt_dance_names"].split(",") if row["alt_dance_names"] else [] songs.append({ "id": row["id"], "title": row["title"], @@ -481,6 +485,7 @@ class MainWindow(QMainWindow): "file_missing": bool(row["file_missing"]), "dances": dances, "dance_levels": levels, + "alt_dances": alt_dances, }) self._library_loaded.emit(songs) except Exception: diff --git a/linedance-app/ui/tag_editor.py b/linedance-app/ui/tag_editor.py index 07b450cb..2bc87392 100644 --- a/linedance-app/ui/tag_editor.py +++ b/linedance-app/ui/tag_editor.py @@ -29,6 +29,16 @@ class DanceLineEdit(QLineEdit): class TagEditorDialog(QDialog): + def keyPressEvent(self, event): + """Forhindre Enter i at lukke dialogen — bruges til at tilføje danse.""" + from PyQt6.QtCore import Qt as _Qt + if event.key() in (_Qt.Key.Key_Return, _Qt.Key.Key_Enter): + # Send Enter videre til det fokuserede widget i stedet for at lukke + focused = self.focusWidget() + if focused and focused is not self: + focused.event(event) + return + super().keyPressEvent(event) def __init__(self, song: dict, parent=None): super().__init__(parent) self._song = song @@ -134,6 +144,7 @@ class TagEditorDialog(QDialog): # Forslags-liste self._dance_suggestions = QListWidget() self._dance_suggestions.setMaximumHeight(120) + self._dance_suggestions.setFocusPolicy(Qt.FocusPolicy.NoFocus) self._dance_suggestions.itemClicked.connect( lambda item: self._add_from_suggestion(item, "dance") ) @@ -152,7 +163,11 @@ class TagEditorDialog(QDialog): return grp def _add_dance_row(self, name="", level_id=None): - from translations import _ + try: + from translations import _, translate_level + except Exception: + def _(key, **kw): return key.split(".")[-1] + def translate_level(n): return n row_widget = QWidget() row_layout = QHBoxLayout(row_widget) row_layout.setContentsMargins(0, 0, 0, 0) @@ -166,7 +181,6 @@ class TagEditorDialog(QDialog): level_cb = QComboBox() level_cb.addItem(_("tags.no_level"), None) for lvl in self._levels: - from translations import translate_level level_cb.addItem(translate_level(lvl["name"]), lvl["id"]) # Sæt til det rigtige niveau if level_id is not None: @@ -221,16 +235,18 @@ class TagEditorDialog(QDialog): pass def _add_from_suggestion(self, item, panel: str): - """Tilføj dans fra forslags-listen ved dobbeltklik.""" + """Tilføj dans fra forslags-listen ved klik.""" name = item.data(Qt.ItemDataRole.UserRole + 1) or item.text().split(" / ")[0] level_id = item.data(Qt.ItemDataRole.UserRole) if panel == "dance": self._add_dance_row(name, level_id) self._new_dance.clear() + self._new_dance.setFocus() self._load_dance_suggestions("", self._dance_suggestions) else: self._add_alt_row(name, level_id) self._new_alt.clear() + self._new_alt.setFocus() self._load_dance_suggestions("", self._alt_suggestions) def _on_add_dance(self): @@ -278,6 +294,7 @@ class TagEditorDialog(QDialog): self._alt_suggestions = QListWidget() self._alt_suggestions.setMaximumHeight(120) + self._alt_suggestions.setFocusPolicy(Qt.FocusPolicy.NoFocus) self._alt_suggestions.itemClicked.connect( lambda item: self._add_from_suggestion(item, "alt") ) @@ -295,7 +312,11 @@ class TagEditorDialog(QDialog): return grp def _add_alt_row(self, name="", level_id=None, note=""): - from translations import _ + try: + from translations import _, translate_level + except Exception: + def _(key, **kw): return key.split(".")[-1] + def translate_level(n): return n row_widget = QWidget() row_layout = QHBoxLayout(row_widget) row_layout.setContentsMargins(0, 0, 0, 0) @@ -313,7 +334,6 @@ class TagEditorDialog(QDialog): level_cb = QComboBox() level_cb.addItem(_("tags.no_level"), None) for lvl in self._levels: - from translations import translate_level level_cb.addItem(translate_level(lvl["name"]), lvl["id"]) if level_id is not None: for i in range(level_cb.count()):