Compare commits

...

13 Commits

Author SHA1 Message Date
Keith Edmunds
589a664971 New template from manage templates correctly marked in db 2025-02-23 17:34:23 +00:00
Keith Edmunds
67bf926ed8 Refactor musicmuster and template management 2025-02-23 17:28:03 +00:00
Keith Edmunds
040020e7ed Refactor playlist management functions 2025-02-23 17:26:43 +00:00
Keith Edmunds
911859ef49 Show red start in tab of templates 2025-02-23 17:24:47 +00:00
Keith Edmunds
68bdff53cf Move menu.yaml into app/ 2025-02-23 09:20:30 +00:00
Keith Edmunds
632937101a WIP dynamic menu for playlist
New playlist shows faves on submenu
2025-02-22 22:27:05 +00:00
Keith Edmunds
639f006a10 Add favourite to playlists 2025-02-22 20:23:07 +00:00
Keith Edmunds
9e27418f80 Remove queries table definition
It mistakenly was introduced to the wrong branch. It persists on the
query_tabs branch.
2025-02-22 20:13:44 +00:00
Keith Edmunds
c1448dfdd5 WIP: manage templates: template rows have different background 2025-02-22 19:42:48 +00:00
Keith Edmunds
5f396a0993 WIP: template management: new, rename, delete working 2025-02-22 19:16:42 +00:00
Keith Edmunds
e10c2adafe WIP: template management: edit and delete working 2025-02-22 11:34:36 +00:00
Keith Edmunds
b0f6e4e819 Framework for dynamic submenus 2025-02-21 15:18:45 +00:00
Keith Edmunds
afd3be608c Move menu definitions to YAML file 2025-02-21 14:16:34 +00:00
12 changed files with 2207 additions and 446 deletions

View File

@ -95,6 +95,7 @@ class Config(object):
PLAY_SETTLE = 500000 PLAY_SETTLE = 500000
PLAYLIST_ICON_CURRENT = ":/icons/green-circle.png" PLAYLIST_ICON_CURRENT = ":/icons/green-circle.png"
PLAYLIST_ICON_NEXT = ":/icons/yellow-circle.png" PLAYLIST_ICON_NEXT = ":/icons/yellow-circle.png"
PLAYLIST_ICON_TEMPLATE = ":/icons/redstar.png"
PREVIEW_ADVANCE_MS = 5000 PREVIEW_ADVANCE_MS = 5000
PREVIEW_BACK_MS = 5000 PREVIEW_BACK_MS = 5000
PREVIEW_END_BUFFER_MS = 1000 PREVIEW_END_BUFFER_MS = 1000

View File

@ -80,8 +80,8 @@ class PlaylistsTable(Model):
cascade="all, delete-orphan", cascade="all, delete-orphan",
order_by="PlaylistRowsTable.row_number", order_by="PlaylistRowsTable.row_number",
) )
query: Mapped["QueriesTable"] = relationship( favourite: Mapped[bool] = mapped_column(
back_populates="playlist", cascade="all, delete-orphan" Boolean, nullable=False, index=False, default=False
) )
def __repr__(self) -> str: def __repr__(self) -> str:
@ -121,20 +121,6 @@ class PlaylistRowsTable(Model):
) )
class QueriesTable(Model):
__tablename__ = "queries"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
query: Mapped[str] = mapped_column(
String(2048), index=False, default="", nullable=False
)
playlist_id: Mapped[int] = mapped_column(ForeignKey("playlists.id"), index=True)
playlist: Mapped[PlaylistsTable] = relationship(back_populates="query")
def __repr__(self) -> str:
return f"<Queries(id={self.id}, playlist={self.playlist}, query={self.query}>"
class SettingsTable(Model): class SettingsTable(Model):
"""Manage settings""" """Manage settings"""

102
app/menu.yaml Normal file
View File

@ -0,0 +1,102 @@
menus:
- title: "&File"
actions:
- text: "Save as Template"
handler: "save_as_template"
- text: "Manage Templates"
handler: "manage_templates"
- separator: true
- separator: true
- text: "Exit"
handler: "close"
- title: "&Playlist"
actions:
- text: "Open Playlist"
handler: "open_existing_playlist"
shortcut: "Ctrl+O"
- text: "New Playlist"
handler: "new_playlist_dynamic_submenu"
submenu: true
- text: "Close Playlist"
handler: "close_playlist_tab"
- text: "Rename Playlist"
handler: "rename_playlist"
- text: "Delete Playlist"
handler: "delete_playlist"
- separator: true
- text: "Insert Track"
handler: "insert_track"
shortcut: "Ctrl+T"
- text: "Select Track from Query"
handler: "query_dynamic_submenu"
submenu: true
- text: "Insert Section Header"
handler: "insert_header"
shortcut: "Ctrl+H"
- text: "Import Files"
handler: "import_files_wrapper"
shortcut: "Ctrl+Shift+I"
- separator: true
- text: "Mark for Moving"
handler: "mark_rows_for_moving"
shortcut: "Ctrl+C"
- text: "Paste"
handler: "paste_rows"
shortcut: "Ctrl+V"
- separator: true
- text: "Export Playlist"
handler: "export_playlist_tab"
- text: "Download CSV of Played Tracks"
handler: "download_played_tracks"
- separator: true
- text: "Select Duplicate Rows"
handler: "select_duplicate_rows"
- text: "Move Selected"
handler: "move_selected"
- text: "Move Unplayed"
handler: "move_unplayed"
- separator: true
- text: "Clear Selection"
handler: "clear_selection"
shortcut: "Esc"
store_reference: true # So we can enable/disable later
- title: "&Music"
actions:
- text: "Set Next"
handler: "set_selected_track_next"
shortcut: "Ctrl+N"
- text: "Play Next"
handler: "play_next"
shortcut: "Return"
- text: "Fade"
handler: "fade"
shortcut: "Ctrl+Z"
- text: "Stop"
handler: "stop"
shortcut: "Ctrl+Alt+S"
- text: "Resume"
handler: "resume"
shortcut: "Ctrl+R"
- text: "Skip to Next"
handler: "play_next"
shortcut: "Ctrl+Alt+Return"
- separator: true
- text: "Search"
handler: "search_playlist"
shortcut: "/"
- text: "Search Title in Wikipedia"
handler: "lookup_row_in_wikipedia"
shortcut: "Ctrl+W"
- text: "Search Title in Songfacts"
handler: "lookup_row_in_songfacts"
shortcut: "Ctrl+S"
- title: "Help"
actions:
- text: "About"
handler: "about"
- text: "Debug"
handler: "debug"

View File

@ -179,12 +179,18 @@ class Playdates(dbtables.PlaydatesTable):
class Playlists(dbtables.PlaylistsTable): class Playlists(dbtables.PlaylistsTable):
def __init__(self, session: Session, name: str): def __init__(self, session: Session, name: str, template_id: int) -> None:
"""Create playlist with passed name"""
self.name = name self.name = name
self.last_used = dt.datetime.now() self.last_used = dt.datetime.now()
session.add(self) session.add(self)
session.commit() session.commit()
# If a template is specified, copy from it
if template_id:
PlaylistRows.copy_playlist(session, template_id, self.id)
@staticmethod @staticmethod
def clear_tabs(session: Session, playlist_ids: list[int]) -> None: def clear_tabs(session: Session, playlist_ids: list[int]) -> None:
""" """
@ -201,26 +207,6 @@ class Playlists(dbtables.PlaylistsTable):
self.open = False self.open = False
session.commit() session.commit()
@classmethod
def create_playlist_from_template(
cls, session: Session, template: "Playlists", playlist_name: str
) -> Optional["Playlists"]:
"""Create a new playlist from template"""
# Sanity check
if not template.id:
return None
playlist = cls(session, playlist_name)
# Sanity / mypy checks
if not playlist or not playlist.id:
return None
PlaylistRows.copy_playlist(session, template.id, playlist.id)
return playlist
def delete(self, session: Session) -> None: def delete(self, session: Session) -> None:
""" """
Delete playlist Delete playlist
@ -247,6 +233,19 @@ class Playlists(dbtables.PlaylistsTable):
select(cls).where(cls.is_template.is_(True)).order_by(cls.name) select(cls).where(cls.is_template.is_(True)).order_by(cls.name)
).all() ).all()
@classmethod
def get_favourite_templates(cls, session: Session) -> Sequence["Playlists"]:
"""Returns a list of favourite templates ordered by name"""
return session.scalars(
select(cls)
.where(
cls.is_template.is_(True),
cls.favourite.is_(True)
)
.order_by(cls.name)
).all()
@classmethod @classmethod
def get_closed(cls, session: Session) -> Sequence["Playlists"]: def get_closed(cls, session: Session) -> Sequence["Playlists"]:
"""Returns a list of all closed playlists ordered by last use""" """Returns a list of all closed playlists ordered by last use"""
@ -301,7 +300,7 @@ class Playlists(dbtables.PlaylistsTable):
) -> None: ) -> None:
"""Save passed playlist as new template""" """Save passed playlist as new template"""
template = Playlists(session, template_name) template = Playlists(session, template_name, template_id=0)
if not template or not template.id: if not template or not template.id:
return return
@ -596,7 +595,7 @@ class PlaylistRows(dbtables.PlaylistRowsTable):
class Settings(dbtables.SettingsTable): class Settings(dbtables.SettingsTable):
def __init__(self, session: Session, name: str): def __init__(self, session: Session, name: str) -> None:
self.name = name self.name = name
session.add(self) session.add(self)
session.commit() session.commit()
@ -624,7 +623,7 @@ class Tracks(dbtables.TracksTable):
fade_at: int, fade_at: int,
silence_at: int, silence_at: int,
bitrate: int, bitrate: int,
): ) -> None:
self.path = path self.path = path
self.title = title self.title = title
self.artist = artist self.artist = artist

File diff suppressed because it is too large Load Diff

View File

@ -26,6 +26,7 @@ from PyQt6.QtGui import (
) )
# Third party imports # Third party imports
from sqlalchemy.orm.session import Session
import obswebsocket # type: ignore import obswebsocket # type: ignore
# import snoop # type: ignore # import snoop # type: ignore
@ -74,12 +75,14 @@ class PlaylistModel(QAbstractTableModel):
def __init__( def __init__(
self, self,
playlist_id: int, playlist_id: int,
is_template: bool,
*args: Optional[QObject], *args: Optional[QObject],
**kwargs: Optional[QObject], **kwargs: Optional[QObject],
) -> None: ) -> None:
log.debug("PlaylistModel.__init__()") log.debug("PlaylistModel.__init__()")
self.playlist_id = playlist_id self.playlist_id = playlist_id
self.is_template = is_template
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.playlist_rows: dict[int, RowAndTrack] = {} self.playlist_rows: dict[int, RowAndTrack] = {}
@ -772,7 +775,7 @@ class PlaylistModel(QAbstractTableModel):
return None return None
def load_data(self, session: db.session) -> None: def load_data(self, session: Session) -> None:
""" """
Same as refresh data, but only used when creating playslit. Same as refresh data, but only used when creating playslit.
Distinguishes profile time between initial load and other Distinguishes profile time between initial load and other
@ -1061,7 +1064,7 @@ class PlaylistModel(QAbstractTableModel):
# Update display # Update display
self.invalidate_row(track_sequence.previous.row_number) self.invalidate_row(track_sequence.previous.row_number)
def refresh_data(self, session: db.session) -> None: def refresh_data(self, session: Session) -> None:
""" """
Populate self.playlist_rows with playlist data Populate self.playlist_rows with playlist data

View File

@ -364,7 +364,7 @@ class PlaylistTab(QTableView):
Override closeEditor to enable play controls and update display. Override closeEditor to enable play controls and update display.
""" """
self.musicmuster.action_Clear_selection.setEnabled(True) self.musicmuster.enable_escape(True)
super(PlaylistTab, self).closeEditor(editor, hint) super(PlaylistTab, self).closeEditor(editor, hint)

View File

@ -1,6 +1,7 @@
<RCC> <RCC>
<qresource prefix="icons"> <qresource prefix="icons">
<file>yellow-circle.png</file> <file>yellow-circle.png</file>
<file>redstar.png</file>
<file>green-circle.png</file> <file>green-circle.png</file>
<file>star.png</file> <file>star.png</file>
<file>star_empty.png</file> <file>star_empty.png</file>

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>1249</width> <width>1249</width>
<height>499</height> <height>538</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">

BIN
app/ui/redstar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -0,0 +1,58 @@
"""add favouirit to playlists
Revision ID: 04df697e40cd
Revises: 33c04e3c12c8
Create Date: 2025-02-22 20:20:45.030024
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
# revision identifiers, used by Alembic.
revision = '04df697e40cd'
down_revision = '33c04e3c12c8'
branch_labels = None
depends_on = None
def upgrade(engine_name: str) -> None:
globals()["upgrade_%s" % engine_name]()
def downgrade(engine_name: str) -> None:
globals()["downgrade_%s" % engine_name]()
def upgrade_() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('notecolours', schema=None) as batch_op:
batch_op.add_column(sa.Column('strip_substring', sa.Boolean(), nullable=False))
batch_op.create_index(batch_op.f('ix_notecolours_substring'), ['substring'], unique=False)
with op.batch_alter_table('playlist_rows', schema=None) as batch_op:
batch_op.drop_constraint('playlist_rows_ibfk_1', type_='foreignkey')
with op.batch_alter_table('playlists', schema=None) as batch_op:
batch_op.add_column(sa.Column('favourite', sa.Boolean(), nullable=False))
# ### end Alembic commands ###
def downgrade_() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('playlists', schema=None) as batch_op:
batch_op.drop_column('favourite')
with op.batch_alter_table('playlist_rows', schema=None) as batch_op:
batch_op.create_foreign_key('playlist_rows_ibfk_1', 'tracks', ['track_id'], ['id'])
with op.batch_alter_table('notecolours', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_notecolours_substring'))
batch_op.drop_column('strip_substring')
# ### end Alembic commands ###