Compare commits
23 Commits
a41aea2d36
...
0978e93ee7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0978e93ee7 | ||
|
|
15f4bec197 | ||
|
|
530ee60015 | ||
|
|
4a6ce3b4ee | ||
|
|
9d3743ceb5 | ||
|
|
bc06722633 | ||
|
|
aa3388f732 | ||
|
|
634637f42c | ||
|
|
613fa4343b | ||
|
|
e23f8afed2 | ||
|
|
9a7d24b895 | ||
|
|
45a564729b | ||
|
|
cc2f3733b2 | ||
|
|
77716005c7 | ||
|
|
fed4e9fbde | ||
|
|
5902428c23 | ||
|
|
58ec47517d | ||
|
|
c14f03f0c1 | ||
|
|
2cd49b5898 | ||
|
|
6de95573ff | ||
|
|
19377a8e1c | ||
|
|
0794f061ee | ||
|
|
1c294e1ce4 |
@ -64,6 +64,7 @@ class Config(object):
|
|||||||
MAX_INFO_TABS = 5
|
MAX_INFO_TABS = 5
|
||||||
MAX_MISSING_FILES_TO_REPORT = 10
|
MAX_MISSING_FILES_TO_REPORT = 10
|
||||||
MILLISECOND_SIGFIGS = 0
|
MILLISECOND_SIGFIGS = 0
|
||||||
|
MINIMUM_ROW_HEIGHT = 30
|
||||||
MYSQL_CONNECT = os.environ.get('MYSQL_CONNECT') or "mysql+mysqldb://musicmuster:musicmuster@localhost/musicmuster_v2" # noqa E501
|
MYSQL_CONNECT = os.environ.get('MYSQL_CONNECT') or "mysql+mysqldb://musicmuster:musicmuster@localhost/musicmuster_v2" # noqa E501
|
||||||
NOTE_TIME_FORMAT = "%H:%M:%S"
|
NOTE_TIME_FORMAT = "%H:%M:%S"
|
||||||
ROOT = os.environ.get('ROOT') or "/home/kae/music"
|
ROOT = os.environ.get('ROOT') or "/home/kae/music"
|
||||||
|
|||||||
@ -45,11 +45,9 @@ def Session() -> Generator[scoped_session, None, None]:
|
|||||||
function = frame.function
|
function = frame.function
|
||||||
lineno = frame.lineno
|
lineno = frame.lineno
|
||||||
Session = scoped_session(sessionmaker(bind=engine, future=True))
|
Session = scoped_session(sessionmaker(bind=engine, future=True))
|
||||||
log.debug(
|
log.debug(f"SqlA: session acquired [{hex(id(Session))}]")
|
||||||
f"Session acquired, {file=}, {function=}, "
|
log.debug(f"Session acquisition: {function}:{lineno} [{hex(id(Session))}]")
|
||||||
f"function{lineno=}, {Session=}"
|
|
||||||
)
|
|
||||||
yield Session
|
yield Session
|
||||||
log.debug(" Session released")
|
log.debug(f" SqlA: session released [{hex(id(Session))}]")
|
||||||
Session.commit()
|
Session.commit()
|
||||||
Session.close()
|
Session.close()
|
||||||
|
|||||||
@ -76,7 +76,7 @@ def log_uncaught_exceptions(_ex_cls, ex, tb):
|
|||||||
logging.critical(''.join(traceback.format_tb(tb)))
|
logging.critical(''.join(traceback.format_tb(tb)))
|
||||||
print("\033[1;37;40m")
|
print("\033[1;37;40m")
|
||||||
print(stackprinter.format(ex, style="darkbg2", add_summary=True))
|
print(stackprinter.format(ex, style="darkbg2", add_summary=True))
|
||||||
if os.environ["MM_ENV"] != "DEVELOPMENT":
|
if os.environ["MM_ENV"] == "PRODUCTION":
|
||||||
msg = stackprinter.format(ex)
|
msg = stackprinter.format(ex)
|
||||||
send_mail(Config.ERRORS_TO, Config.ERRORS_FROM,
|
send_mail(Config.ERRORS_TO, Config.ERRORS_FROM,
|
||||||
"Exception from musicmuster", msg)
|
"Exception from musicmuster", msg)
|
||||||
|
|||||||
@ -52,11 +52,11 @@ class Carts(Base):
|
|||||||
__tablename__ = 'carts'
|
__tablename__ = 'carts'
|
||||||
|
|
||||||
id: int = Column(Integer, primary_key=True, autoincrement=True)
|
id: int = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
cart_number = Column(Integer, nullable=False, unique=True)
|
cart_number: int = Column(Integer, nullable=False, unique=True)
|
||||||
name = Column(String(256), index=True)
|
name = Column(String(256), index=True)
|
||||||
duration = Column(Integer, index=True)
|
duration = Column(Integer, index=True)
|
||||||
path = Column(String(2048), index=False)
|
path = Column(String(2048), index=False)
|
||||||
enabled = Column(Boolean, default=False, nullable=False)
|
enabled: bool = Column(Boolean, default=False, nullable=False)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (
|
return (
|
||||||
@ -192,13 +192,13 @@ class Playlists(Base):
|
|||||||
__tablename__ = "playlists"
|
__tablename__ = "playlists"
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True, autoincrement=True, nullable=False)
|
id = Column(Integer, primary_key=True, autoincrement=True, nullable=False)
|
||||||
name = Column(String(32), nullable=False, unique=True)
|
name: str = Column(String(32), nullable=False, unique=True)
|
||||||
last_used = Column(DateTime, default=None, nullable=True)
|
last_used = Column(DateTime, default=None, nullable=True)
|
||||||
tab = Column(Integer, default=None, nullable=True, unique=True)
|
tab = Column(Integer, default=None, nullable=True, unique=True)
|
||||||
sort_column = Column(Integer, default=None, nullable=True, unique=False)
|
sort_column = Column(Integer, default=None, nullable=True, unique=False)
|
||||||
is_template = Column(Boolean, default=False, nullable=False)
|
is_template: bool = Column(Boolean, default=False, nullable=False)
|
||||||
query = Column(String(256), default=None, nullable=True, unique=False)
|
query = Column(String(256), default=None, nullable=True, unique=False)
|
||||||
deleted = Column(Boolean, default=False, nullable=False)
|
deleted: bool = Column(Boolean, default=False, nullable=False)
|
||||||
rows: List["PlaylistRows"] = relationship(
|
rows: List["PlaylistRows"] = relationship(
|
||||||
"PlaylistRows",
|
"PlaylistRows",
|
||||||
back_populates="playlist",
|
back_populates="playlist",
|
||||||
@ -371,14 +371,15 @@ class Playlists(Base):
|
|||||||
class PlaylistRows(Base):
|
class PlaylistRows(Base):
|
||||||
__tablename__ = 'playlist_rows'
|
__tablename__ = 'playlist_rows'
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
id: int = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
row_number = Column(Integer, nullable=False)
|
row_number: int = Column(Integer, nullable=False)
|
||||||
note = Column(String(2048), index=False)
|
note: str = Column(String(2048), index=False, default="", nullable=False)
|
||||||
playlist_id = Column(Integer, ForeignKey('playlists.id'), nullable=False)
|
playlist_id: int = Column(Integer, ForeignKey('playlists.id'),
|
||||||
|
nullable=False)
|
||||||
playlist: Playlists = relationship(Playlists, back_populates="rows")
|
playlist: Playlists = relationship(Playlists, back_populates="rows")
|
||||||
track_id = Column(Integer, ForeignKey('tracks.id'), nullable=True)
|
track_id = Column(Integer, ForeignKey('tracks.id'), nullable=True)
|
||||||
track: "Tracks" = relationship("Tracks", back_populates="playlistrows")
|
track: "Tracks" = relationship("Tracks", back_populates="playlistrows")
|
||||||
played = Column(Boolean, nullable=False, index=False, default=False)
|
played: bool = Column(Boolean, nullable=False, index=False, default=False)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (
|
return (
|
||||||
@ -392,7 +393,7 @@ class PlaylistRows(Base):
|
|||||||
playlist_id: int,
|
playlist_id: int,
|
||||||
track_id: Optional[int],
|
track_id: Optional[int],
|
||||||
row_number: int,
|
row_number: int,
|
||||||
note: Optional[str] = None
|
note: str = ""
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Create PlaylistRows object"""
|
"""Create PlaylistRows object"""
|
||||||
|
|
||||||
@ -428,9 +429,8 @@ class PlaylistRows(Base):
|
|||||||
plr.note)
|
plr.note)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def delete_plrids_not_in_list(
|
def delete_higher_rows(
|
||||||
session: scoped_session, playlist_id: int,
|
session: scoped_session, playlist_id: int, maxrow: int) -> None:
|
||||||
plr_ids: Union[Iterable[int], ValuesView]) -> None:
|
|
||||||
"""
|
"""
|
||||||
Delete rows in given playlist that have a higher row number
|
Delete rows in given playlist that have a higher row number
|
||||||
than 'maxrow'
|
than 'maxrow'
|
||||||
@ -440,11 +440,10 @@ class PlaylistRows(Base):
|
|||||||
delete(PlaylistRows)
|
delete(PlaylistRows)
|
||||||
.where(
|
.where(
|
||||||
PlaylistRows.playlist_id == playlist_id,
|
PlaylistRows.playlist_id == playlist_id,
|
||||||
PlaylistRows.id.not_in(plr_ids)
|
PlaylistRows.row_number > maxrow
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
# Delete won't take effect until commit()
|
session.flush()
|
||||||
session.commit()
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def fixup_rownumbers(session: scoped_session, playlist_id: int) -> None:
|
def fixup_rownumbers(session: scoped_session, playlist_id: int) -> None:
|
||||||
@ -464,6 +463,27 @@ class PlaylistRows(Base):
|
|||||||
# Ensure new row numbers are available to the caller
|
# Ensure new row numbers are available to the caller
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_section_header_rows(cls, session: scoped_session,
|
||||||
|
playlist_id: int) -> List["PlaylistRows"]:
|
||||||
|
"""
|
||||||
|
Return a list of PlaylistRows that are section headers for this
|
||||||
|
playlist
|
||||||
|
"""
|
||||||
|
|
||||||
|
plrs = session.execute(
|
||||||
|
select(cls)
|
||||||
|
.where(
|
||||||
|
cls.playlist_id == playlist_id,
|
||||||
|
cls.track_id.is_(None),
|
||||||
|
(
|
||||||
|
cls.note.endswith("-") |
|
||||||
|
cls.note.endswith("+")
|
||||||
|
)
|
||||||
|
).order_by(cls.row_number)).scalars().all()
|
||||||
|
|
||||||
|
return plrs
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_track_plr(session: scoped_session, track_id: int,
|
def get_track_plr(session: scoped_session, track_id: int,
|
||||||
playlist_id: int) -> Optional["PlaylistRows"]:
|
playlist_id: int) -> Optional["PlaylistRows"]:
|
||||||
@ -508,21 +528,27 @@ class PlaylistRows(Base):
|
|||||||
return plrs
|
return plrs
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_rows_with_tracks(cls, session: scoped_session,
|
def get_rows_with_tracks(
|
||||||
playlist_id: int) -> List["PlaylistRows"]:
|
cls, session: scoped_session, playlist_id: int,
|
||||||
|
from_row: Optional[int] = None,
|
||||||
|
to_row: Optional[int] = None) -> List["PlaylistRows"]:
|
||||||
"""
|
"""
|
||||||
For passed playlist, return a list of rows that
|
For passed playlist, return a list of rows that
|
||||||
contain tracks
|
contain tracks
|
||||||
"""
|
"""
|
||||||
|
|
||||||
plrs = session.execute(
|
query = select(cls).where(
|
||||||
select(cls)
|
cls.playlist_id == playlist_id,
|
||||||
.where(
|
cls.track_id.is_not(None)
|
||||||
cls.playlist_id == playlist_id,
|
)
|
||||||
cls.track_id.is_not(None)
|
if from_row is not None:
|
||||||
)
|
query = query.where(cls.row_number >= from_row)
|
||||||
.order_by(cls.row_number)
|
if to_row is not None:
|
||||||
).scalars().all()
|
query = query.where(cls.row_number <= to_row)
|
||||||
|
|
||||||
|
plrs = (
|
||||||
|
session.execute((query).order_by(cls.row_number)).scalars().all()
|
||||||
|
)
|
||||||
|
|
||||||
return plrs
|
return plrs
|
||||||
|
|
||||||
@ -637,7 +663,7 @@ class Tracks(Base):
|
|||||||
start_gap = Column(Integer, index=False)
|
start_gap = Column(Integer, index=False)
|
||||||
fade_at = Column(Integer, index=False)
|
fade_at = Column(Integer, index=False)
|
||||||
silence_at = Column(Integer, index=False)
|
silence_at = Column(Integer, index=False)
|
||||||
path = Column(String(2048), index=False, nullable=False, unique=True)
|
path: str = Column(String(2048), index=False, nullable=False, unique=True)
|
||||||
mtime = Column(Float, index=True)
|
mtime = Column(Float, index=True)
|
||||||
bitrate = Column(Integer, nullable=True, default=None)
|
bitrate = Column(Integer, nullable=True, default=None)
|
||||||
playlistrows: PlaylistRows = relationship("PlaylistRows",
|
playlistrows: PlaylistRows = relationship("PlaylistRows",
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
from log import log
|
from log import log
|
||||||
from os.path import basename
|
from os.path import basename
|
||||||
import argparse
|
import argparse
|
||||||
|
import os
|
||||||
import stackprinter # type: ignore
|
import stackprinter # type: ignore
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
@ -248,6 +249,17 @@ class ImportTrack(QObject):
|
|||||||
self.finished.emit(self.playlist)
|
self.finished.emit(self.playlist)
|
||||||
|
|
||||||
|
|
||||||
|
class MusicMusterSignals(QObject):
|
||||||
|
"""
|
||||||
|
Class for all MusicMuster signals. See:
|
||||||
|
- https://zetcode.com/gui/pyqt5/eventssignals/
|
||||||
|
- https://stackoverflow.com/questions/62654525/
|
||||||
|
emit-a-signal-from-another-class-to-main-class
|
||||||
|
"""
|
||||||
|
|
||||||
|
save_playlist_signal = pyqtSignal()
|
||||||
|
|
||||||
|
|
||||||
class Window(QMainWindow, Ui_MainWindow):
|
class Window(QMainWindow, Ui_MainWindow):
|
||||||
def __init__(self, parent=None, *args, **kwargs) -> None:
|
def __init__(self, parent=None, *args, **kwargs) -> None:
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
@ -266,6 +278,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
self.previous_track_position: Optional[float] = None
|
self.previous_track_position: Optional[float] = None
|
||||||
self.selected_plrs: Optional[List[PlaylistRows]] = None
|
self.selected_plrs: Optional[List[PlaylistRows]] = None
|
||||||
|
|
||||||
|
self.signals = MusicMusterSignals()
|
||||||
|
|
||||||
# Set colours that will be used by playlist row stripes
|
# Set colours that will be used by playlist row stripes
|
||||||
palette = QPalette()
|
palette = QPalette()
|
||||||
palette.setColor(QPalette.Base, QColor(Config.COLOUR_EVEN_PLAYLIST))
|
palette.setColor(QPalette.Base, QColor(Config.COLOUR_EVEN_PLAYLIST))
|
||||||
@ -433,6 +447,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
self.next_track = PlaylistTrack()
|
self.next_track = PlaylistTrack()
|
||||||
|
self.update_headers()
|
||||||
|
|
||||||
def clear_selection(self) -> None:
|
def clear_selection(self) -> None:
|
||||||
""" Clear selected row"""
|
""" Clear selected row"""
|
||||||
@ -513,11 +528,9 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
"Can't close current track playlist", 5000)
|
"Can't close current track playlist", 5000)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Don't close next track playlist
|
# Attempt to close next track playlist
|
||||||
if self.tabPlaylist.widget(tab_index) == self.next_track.playlist_tab:
|
if self.tabPlaylist.widget(tab_index) == self.next_track.playlist_tab:
|
||||||
self.statusbar.showMessage(
|
self.next_track.playlist_tab.mark_unnext()
|
||||||
"Can't close next track playlist", 5000)
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Record playlist as closed and update remaining playlist tabs
|
# Record playlist as closed and update remaining playlist tabs
|
||||||
with Session() as session:
|
with Session() as session:
|
||||||
@ -586,13 +599,15 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
|
|
||||||
def create_playlist(self,
|
def create_playlist(self,
|
||||||
session: scoped_session,
|
session: scoped_session,
|
||||||
playlist_name: Optional[str] = None) -> Playlists:
|
playlist_name: Optional[str] = None) \
|
||||||
|
-> Optional[Playlists]:
|
||||||
"""Create new playlist"""
|
"""Create new playlist"""
|
||||||
|
|
||||||
while not playlist_name:
|
playlist_name = self.solicit_playlist_name()
|
||||||
playlist_name = self.solicit_playlist_name()
|
if not playlist_name:
|
||||||
|
return None
|
||||||
playlist = Playlists(session, playlist_name)
|
playlist = Playlists(session, playlist_name)
|
||||||
|
|
||||||
return playlist
|
return playlist
|
||||||
|
|
||||||
def create_and_show_playlist(self) -> None:
|
def create_and_show_playlist(self) -> None:
|
||||||
@ -613,7 +628,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
assert playlist.id
|
assert playlist.id
|
||||||
|
|
||||||
playlist_tab = PlaylistTab(
|
playlist_tab = PlaylistTab(
|
||||||
musicmuster=self, session=session, playlist_id=playlist.id)
|
musicmuster=self, session=session, playlist_id=playlist.id,
|
||||||
|
signals=self.signals)
|
||||||
idx = self.tabPlaylist.addTab(playlist_tab, playlist.name)
|
idx = self.tabPlaylist.addTab(playlist_tab, playlist.name)
|
||||||
self.tabPlaylist.setCurrentIndex(idx)
|
self.tabPlaylist.setCurrentIndex(idx)
|
||||||
|
|
||||||
@ -633,6 +649,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
def debug(self):
|
def debug(self):
|
||||||
"""Invoke debugger"""
|
"""Invoke debugger"""
|
||||||
|
|
||||||
|
visible_playlist_id = self.visible_playlist_tab().playlist_id
|
||||||
|
print(f"Active playlist id={visible_playlist_id}")
|
||||||
import ipdb # type: ignore
|
import ipdb # type: ignore
|
||||||
ipdb.set_trace()
|
ipdb.set_trace()
|
||||||
|
|
||||||
@ -721,17 +739,20 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
# doesn't see player=None and kick off end-of-track actions
|
# doesn't see player=None and kick off end-of-track actions
|
||||||
self.playing = False
|
self.playing = False
|
||||||
|
|
||||||
|
# Remove currently playing track colour
|
||||||
|
if (
|
||||||
|
self.current_track and
|
||||||
|
self.current_track.playlist_tab and
|
||||||
|
self.current_track.plr_id
|
||||||
|
):
|
||||||
|
self.current_track.playlist_tab.reset_plr_row_colour(
|
||||||
|
self.current_track.plr_id)
|
||||||
|
|
||||||
# Reset PlaylistTrack objects
|
# Reset PlaylistTrack objects
|
||||||
if self.current_track.track_id:
|
if self.current_track.track_id:
|
||||||
self.previous_track = self.current_track
|
self.previous_track = self.current_track
|
||||||
self.current_track = PlaylistTrack()
|
self.current_track = PlaylistTrack()
|
||||||
|
|
||||||
# Repaint playlist to remove currently playing track colour
|
|
||||||
# What was current track is now previous track
|
|
||||||
with Session() as session:
|
|
||||||
if self.previous_track.playlist_tab:
|
|
||||||
self.previous_track.playlist_tab.update_display(session)
|
|
||||||
|
|
||||||
# Reset clocks
|
# Reset clocks
|
||||||
self.frame_fade.setStyleSheet("")
|
self.frame_fade.setStyleSheet("")
|
||||||
self.frame_silent.setStyleSheet("")
|
self.frame_silent.setStyleSheet("")
|
||||||
@ -855,7 +876,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
# Update all displayed playlists
|
# Update all displayed playlists
|
||||||
with Session() as session:
|
with Session() as session:
|
||||||
for i in range(self.tabPlaylist.count()):
|
for i in range(self.tabPlaylist.count()):
|
||||||
self.tabPlaylist.widget(i).update_display(session)
|
self.tabPlaylist.widget(i).hide_played_tracks(
|
||||||
|
self.hide_played_tracks)
|
||||||
|
|
||||||
def import_track(self) -> None:
|
def import_track(self) -> None:
|
||||||
"""Import track file"""
|
"""Import track file"""
|
||||||
@ -926,8 +948,6 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
self.statusbar.showMessage("Imports complete")
|
self.statusbar.showMessage("Imports complete")
|
||||||
with Session() as session:
|
|
||||||
playlist_tab.update_display(session)
|
|
||||||
|
|
||||||
def insert_header(self) -> None:
|
def insert_header(self) -> None:
|
||||||
"""Show dialog box to enter header text and add to playlist"""
|
"""Show dialog box to enter header text and add to playlist"""
|
||||||
@ -1248,14 +1268,16 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
self.disable_play_next_controls()
|
self.disable_play_next_controls()
|
||||||
|
|
||||||
# If previous track playlist is showing and that's not the
|
# If previous track playlist is showing and that's not the
|
||||||
# current track playlist, we need to update the display to
|
# current track playlist, we need to reset the current track
|
||||||
# reset the current track highlighting
|
# highlighting
|
||||||
if (
|
if (
|
||||||
self.previous_track.playlist_tab == self.visible_playlist_tab()
|
self.previous_track.playlist_tab == self.visible_playlist_tab()
|
||||||
and
|
and
|
||||||
self.current_track.playlist_tab != self.visible_playlist_tab()
|
self.current_track.playlist_tab != self.visible_playlist_tab()
|
||||||
|
and self.previous_track.plr_id
|
||||||
):
|
):
|
||||||
self.visible_playlist_tab().update_display(session)
|
self.previous_track.playlist_tab.reset_plr_row_colour(
|
||||||
|
self.previous_track.plr_id)
|
||||||
|
|
||||||
# Update headers
|
# Update headers
|
||||||
self.update_headers()
|
self.update_headers()
|
||||||
@ -1541,16 +1563,10 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
|
|
||||||
self.next_track.set_plr(session, plr, playlist_tab)
|
self.next_track.set_plr(session, plr, playlist_tab)
|
||||||
if self.next_track.playlist_tab:
|
if self.next_track.playlist_tab:
|
||||||
self.next_track.playlist_tab.update_display(session)
|
|
||||||
if self.current_track.playlist_tab != self.next_track.playlist_tab:
|
if self.current_track.playlist_tab != self.next_track.playlist_tab:
|
||||||
self.set_tab_colour(self.next_track.playlist_tab,
|
self.set_tab_colour(self.next_track.playlist_tab,
|
||||||
QColor(Config.COLOUR_NEXT_TAB))
|
QColor(Config.COLOUR_NEXT_TAB))
|
||||||
|
|
||||||
# If we've changed playlist tabs for next track, refresh old one
|
|
||||||
# to remove highligting of next track
|
|
||||||
if original_next_track_playlist_tab:
|
|
||||||
original_next_track_playlist_tab.update_display(session)
|
|
||||||
|
|
||||||
# Populate footer if we're not currently playing
|
# Populate footer if we're not currently playing
|
||||||
if not self.playing and self.next_track.track_id:
|
if not self.playing and self.next_track.track_id:
|
||||||
self.label_track_length.setText(
|
self.label_track_length.setText(
|
||||||
@ -1965,11 +1981,12 @@ if __name__ == "__main__":
|
|||||||
engine.dispose()
|
engine.dispose()
|
||||||
sys.exit(status)
|
sys.exit(status)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
from helpers import send_mail
|
if os.environ["MM_ENV"] == "PRODUCTION":
|
||||||
|
from helpers import send_mail
|
||||||
|
|
||||||
msg = stackprinter.format(exc)
|
msg = stackprinter.format(exc)
|
||||||
send_mail(Config.ERRORS_TO, Config.ERRORS_FROM,
|
send_mail(Config.ERRORS_TO, Config.ERRORS_FROM,
|
||||||
"Exception from musicmuster", msg)
|
"Exception from musicmuster", msg)
|
||||||
|
|
||||||
print("\033[1;31;47mUnhandled exception starts")
|
print("\033[1;31;47mUnhandled exception starts")
|
||||||
stackprinter.show(style="darkbg")
|
stackprinter.show(style="darkbg")
|
||||||
|
|||||||
1499
app/playlists.py
1499
app/playlists.py
File diff suppressed because it is too large
Load Diff
110
tree.py
Executable file
110
tree.py
Executable file
@ -0,0 +1,110 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
|
|
||||||
|
datas = {
|
||||||
|
"Category 1": [
|
||||||
|
("New Game 2", "Playnite", "", "", "Never", "Not Played", ""),
|
||||||
|
("New Game 3", "Playnite", "", "", "Never", "Not Played", ""),
|
||||||
|
],
|
||||||
|
"No Category": [
|
||||||
|
("New Game", "Playnite", "", "", "Never", "Not Plated", ""),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
class GroupDelegate(QtWidgets.QStyledItemDelegate):
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super(GroupDelegate, self).__init__(parent)
|
||||||
|
self._plus_icon = QtGui.QIcon("plus.png")
|
||||||
|
self._minus_icon = QtGui.QIcon("minus.png")
|
||||||
|
|
||||||
|
def initStyleOption(self, option, index):
|
||||||
|
super(GroupDelegate, self).initStyleOption(option, index)
|
||||||
|
if not index.parent().isValid():
|
||||||
|
is_open = bool(option.state & QtWidgets.QStyle.State_Open)
|
||||||
|
option.features |= QtWidgets.QStyleOptionViewItem.HasDecoration
|
||||||
|
option.icon = self._minus_icon if is_open else self._plus_icon
|
||||||
|
|
||||||
|
class GroupView(QtWidgets.QTreeView):
|
||||||
|
def __init__(self, model, parent=None):
|
||||||
|
super(GroupView, self).__init__(parent)
|
||||||
|
self.setIndentation(0)
|
||||||
|
self.setExpandsOnDoubleClick(False)
|
||||||
|
self.clicked.connect(self.on_clicked)
|
||||||
|
delegate = GroupDelegate(self)
|
||||||
|
self.setItemDelegateForColumn(0, delegate)
|
||||||
|
self.setModel(model)
|
||||||
|
self.header().setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeToContents)
|
||||||
|
self.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
|
||||||
|
self.setStyleSheet("background-color: #0D1225;")
|
||||||
|
|
||||||
|
@QtCore.pyqtSlot(QtCore.QModelIndex)
|
||||||
|
def on_clicked(self, index):
|
||||||
|
if not index.parent().isValid() and index.column() == 0:
|
||||||
|
self.setExpanded(index, not self.isExpanded(index))
|
||||||
|
|
||||||
|
|
||||||
|
class GroupModel(QtGui.QStandardItemModel):
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super(GroupModel, self).__init__(parent)
|
||||||
|
self.setColumnCount(8)
|
||||||
|
self.setHorizontalHeaderLabels(["", "Name", "Library", "Release Date", "Genre(s)", "Last Played", "Time Played", ""])
|
||||||
|
for i in range(self.columnCount()):
|
||||||
|
it = self.horizontalHeaderItem(i)
|
||||||
|
it.setForeground(QtGui.QColor("#F2F2F2"))
|
||||||
|
|
||||||
|
def add_group(self, group_name):
|
||||||
|
item_root = QtGui.QStandardItem()
|
||||||
|
item_root.setEditable(False)
|
||||||
|
item = QtGui.QStandardItem(group_name)
|
||||||
|
item.setEditable(False)
|
||||||
|
ii = self.invisibleRootItem()
|
||||||
|
i = ii.rowCount()
|
||||||
|
for j, it in enumerate((item_root, item)):
|
||||||
|
ii.setChild(i, j, it)
|
||||||
|
ii.setEditable(False)
|
||||||
|
for j in range(self.columnCount()):
|
||||||
|
it = ii.child(i, j)
|
||||||
|
if it is None:
|
||||||
|
it = QtGui.QStandardItem()
|
||||||
|
ii.setChild(i, j, it)
|
||||||
|
it.setBackground(QtGui.QColor("#002842"))
|
||||||
|
it.setForeground(QtGui.QColor("#F2F2F2"))
|
||||||
|
return item_root
|
||||||
|
|
||||||
|
def append_element_to_group(self, group_item, texts):
|
||||||
|
j = group_item.rowCount()
|
||||||
|
item_icon = QtGui.QStandardItem()
|
||||||
|
item_icon.setEditable(False)
|
||||||
|
item_icon.setIcon(QtGui.QIcon("game.png"))
|
||||||
|
item_icon.setBackground(QtGui.QColor("#0D1225"))
|
||||||
|
group_item.setChild(j, 0, item_icon)
|
||||||
|
for i, text in enumerate(texts):
|
||||||
|
item = QtGui.QStandardItem(text)
|
||||||
|
item.setEditable(False)
|
||||||
|
item.setBackground(QtGui.QColor("#0D1225"))
|
||||||
|
item.setForeground(QtGui.QColor("#F2F2F2"))
|
||||||
|
group_item.setChild(j, i+1, item)
|
||||||
|
|
||||||
|
|
||||||
|
class MainWindow(QtWidgets.QMainWindow):
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super(MainWindow, self).__init__(parent)
|
||||||
|
|
||||||
|
model = GroupModel(self)
|
||||||
|
tree_view = GroupView(model)
|
||||||
|
self.setCentralWidget(tree_view)
|
||||||
|
|
||||||
|
for group, childrens in datas.items():
|
||||||
|
group_item = model.add_group(group)
|
||||||
|
for children in childrens:
|
||||||
|
model.append_element_to_group(group_item, children)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import sys
|
||||||
|
app = QtWidgets.QApplication(sys.argv)
|
||||||
|
w = MainWindow()
|
||||||
|
w.resize(720, 240)
|
||||||
|
w.show()
|
||||||
|
sys.exit(app.exec_())
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user