Compare commits
7 Commits
ffa3015ac3
...
f182f49f15
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f182f49f15 | ||
|
|
5d50ebf3aa | ||
|
|
73bb4b3a7f | ||
|
|
dfb9326d5e | ||
|
|
e736cb82d2 | ||
|
|
d471082e3f | ||
|
|
e77c05b908 |
@ -5,21 +5,17 @@ import smtplib
|
|||||||
import ssl
|
import ssl
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
from email.message import EmailMessage
|
|
||||||
from mutagen.flac import FLAC # type: ignore
|
|
||||||
from mutagen.mp3 import MP3 # type: ignore
|
|
||||||
from pydub import effects
|
|
||||||
from pydub.utils import mediainfo
|
|
||||||
|
|
||||||
from config import Config
|
from config import Config
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from email.message import EmailMessage
|
||||||
from log import log
|
from log import log
|
||||||
from pydub import AudioSegment
|
from mutagen.flac import FLAC # type: ignore
|
||||||
|
from mutagen.mp3 import MP3 # type: ignore
|
||||||
|
from pydub import AudioSegment, effects
|
||||||
|
from pydub.utils import mediainfo
|
||||||
from PyQt5.QtWidgets import QMessageBox
|
from PyQt5.QtWidgets import QMessageBox
|
||||||
from tinytag import TinyTag # type: ignore
|
from tinytag import TinyTag # type: ignore
|
||||||
from typing import Optional
|
from typing import Any, Dict, Optional, Union
|
||||||
# from typing import Dict, Optional, Union
|
|
||||||
from typing import Dict, Union
|
|
||||||
|
|
||||||
|
|
||||||
def ask_yes_no(title: str, question: str) -> bool:
|
def ask_yes_no(title: str, question: str) -> bool:
|
||||||
@ -82,7 +78,7 @@ def get_audio_segment(path: str) -> Optional[AudioSegment]:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_tags(path: str) -> Dict[str, Union[str, int]]:
|
def get_tags(path: str) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Return a dictionary of title, artist, duration-in-milliseconds and path.
|
Return a dictionary of title, artist, duration-in-milliseconds and path.
|
||||||
"""
|
"""
|
||||||
@ -98,7 +94,7 @@ def get_tags(path: str) -> Dict[str, Union[str, int]]:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_relative_date(past_date: datetime,
|
def get_relative_date(past_date: Optional[datetime],
|
||||||
reference_date: Optional[datetime] = None) -> str:
|
reference_date: Optional[datetime] = None) -> str:
|
||||||
"""
|
"""
|
||||||
Return how long before reference_date past_date is as string.
|
Return how long before reference_date past_date is as string.
|
||||||
@ -189,7 +185,8 @@ def send_mail(to_addr, from_addr, subj, body):
|
|||||||
s.quit()
|
s.quit()
|
||||||
|
|
||||||
|
|
||||||
def ms_to_mmss(ms: int, decimals: int = 0, negative: bool = False) -> str:
|
def ms_to_mmss(ms: Optional[int], decimals: int = 0,
|
||||||
|
negative: bool = False) -> str:
|
||||||
"""Convert milliseconds to mm:ss"""
|
"""Convert milliseconds to mm:ss"""
|
||||||
|
|
||||||
minutes: int
|
minutes: int
|
||||||
|
|||||||
@ -63,7 +63,7 @@ class Carts(Base):
|
|||||||
|
|
||||||
def __init__(self, session: scoped_session, cart_number: int,
|
def __init__(self, session: scoped_session, cart_number: int,
|
||||||
name: Optional[str] = None,
|
name: Optional[str] = None,
|
||||||
duration: int = None, path: str = None,
|
duration: Optional[int] = None, path: Optional[str] = None,
|
||||||
enabled: bool = True) -> None:
|
enabled: bool = True) -> None:
|
||||||
"""Create new cart"""
|
"""Create new cart"""
|
||||||
|
|
||||||
@ -132,7 +132,7 @@ class Playdates(Base):
|
|||||||
id: int = Column(Integer, primary_key=True, autoincrement=True)
|
id: int = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
lastplayed = Column(DateTime, index=True, default=None)
|
lastplayed = Column(DateTime, index=True, default=None)
|
||||||
track_id = Column(Integer, ForeignKey('tracks.id'))
|
track_id = Column(Integer, ForeignKey('tracks.id'))
|
||||||
track = relationship("Tracks", back_populates="playdates")
|
track: "Tracks" = relationship("Tracks", back_populates="playdates")
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (
|
return (
|
||||||
@ -149,7 +149,8 @@ class Playdates(Base):
|
|||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def last_played(session: scoped_session, track_id: int) -> Optional[datetime]:
|
def last_played(session: scoped_session,
|
||||||
|
track_id: int) -> Optional[datetime]:
|
||||||
"""Return datetime track last played or None"""
|
"""Return datetime track last played or None"""
|
||||||
|
|
||||||
last_played = session.execute(
|
last_played = session.execute(
|
||||||
@ -165,7 +166,8 @@ class Playdates(Base):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def played_after(session: scoped_session, since: datetime) -> List["Playdates"]:
|
def played_after(session: scoped_session,
|
||||||
|
since: datetime) -> List["Playdates"]:
|
||||||
"""Return a list of Playdates objects since passed time"""
|
"""Return a list of Playdates objects since passed time"""
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -186,7 +188,7 @@ class Playlists(Base):
|
|||||||
|
|
||||||
__tablename__ = "playlists"
|
__tablename__ = "playlists"
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
id = Column(Integer, primary_key=True, autoincrement=True, nullable=False)
|
||||||
name = Column(String(32), nullable=False, unique=True)
|
name = 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)
|
||||||
@ -194,7 +196,7 @@ class Playlists(Base):
|
|||||||
is_template = Column(Boolean, default=False, nullable=False)
|
is_template = 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 = Column(Boolean, default=False, nullable=False)
|
||||||
rows = relationship(
|
rows: "PlaylistRows" = relationship(
|
||||||
"PlaylistRows",
|
"PlaylistRows",
|
||||||
back_populates="playlist",
|
back_populates="playlist",
|
||||||
cascade="all, delete-orphan",
|
cascade="all, delete-orphan",
|
||||||
@ -207,10 +209,10 @@ class Playlists(Base):
|
|||||||
f"is_templatee={self.is_template}>"
|
f"is_templatee={self.is_template}>"
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, session: scoped_session, name: str) -> None:
|
def __init__(self, session: scoped_session, name: str):
|
||||||
self.name = name
|
self.name = name
|
||||||
session.add(self)
|
session.add(self)
|
||||||
session.commit()
|
session.flush()
|
||||||
|
|
||||||
def close(self, session: scoped_session) -> None:
|
def close(self, session: scoped_session) -> None:
|
||||||
"""Mark playlist as unloaded"""
|
"""Mark playlist as unloaded"""
|
||||||
@ -231,10 +233,15 @@ class Playlists(Base):
|
|||||||
session: scoped_session,
|
session: scoped_session,
|
||||||
template: "Playlists",
|
template: "Playlists",
|
||||||
playlist_name: str) \
|
playlist_name: str) \
|
||||||
-> "Playlists":
|
-> Optional["Playlists"]:
|
||||||
"""Create a new playlist from template"""
|
"""Create a new playlist from template"""
|
||||||
|
|
||||||
playlist = cls(session, playlist_name)
|
playlist = cls(session, playlist_name)
|
||||||
|
|
||||||
|
# Sanity / mypy checks
|
||||||
|
if not playlist or not playlist.id or not template.id:
|
||||||
|
return None
|
||||||
|
|
||||||
PlaylistRows.copy_playlist(session, template.id, playlist.id)
|
PlaylistRows.copy_playlist(session, template.id, playlist.id)
|
||||||
|
|
||||||
return playlist
|
return playlist
|
||||||
@ -332,6 +339,9 @@ class Playlists(Base):
|
|||||||
"""Save passed playlist as new template"""
|
"""Save passed playlist as new template"""
|
||||||
|
|
||||||
template = Playlists(session, template_name)
|
template = Playlists(session, template_name)
|
||||||
|
if not template or not template.id:
|
||||||
|
return
|
||||||
|
|
||||||
template.is_template = True
|
template.is_template = True
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
@ -345,9 +355,9 @@ class PlaylistRows(Base):
|
|||||||
row_number = Column(Integer, nullable=False)
|
row_number = Column(Integer, nullable=False)
|
||||||
note = Column(String(2048), index=False)
|
note = Column(String(2048), index=False)
|
||||||
playlist_id = Column(Integer, ForeignKey('playlists.id'), nullable=False)
|
playlist_id = Column(Integer, ForeignKey('playlists.id'), nullable=False)
|
||||||
playlist = 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 = relationship("Tracks", back_populates="playlistrows")
|
track: "Tracks" = relationship("Tracks", back_populates="playlistrows")
|
||||||
played = Column(Boolean, nullable=False, index=False, default=False)
|
played = Column(Boolean, nullable=False, index=False, default=False)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
@ -448,7 +458,8 @@ class PlaylistRows(Base):
|
|||||||
).first()
|
).first()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_last_used_row(session: scoped_session, playlist_id: int) -> Optional[int]:
|
def get_last_used_row(session: scoped_session,
|
||||||
|
playlist_id: int) -> Optional[int]:
|
||||||
"""Return the last used row for playlist, or None if no rows"""
|
"""Return the last used row for playlist, or None if no rows"""
|
||||||
|
|
||||||
return session.execute(
|
return session.execute(
|
||||||
@ -515,8 +526,8 @@ class PlaylistRows(Base):
|
|||||||
return plrs
|
return plrs
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def move_rows_down(session: scoped_session, playlist_id: int, starting_row: int,
|
def move_rows_down(session: scoped_session, playlist_id: int,
|
||||||
move_by: int) -> None:
|
starting_row: int, move_by: int) -> None:
|
||||||
"""
|
"""
|
||||||
Create space to insert move_by additional rows by incremented row
|
Create space to insert move_by additional rows by incremented row
|
||||||
number from starting_row to end of playlist
|
number from starting_row to end of playlist
|
||||||
@ -567,27 +578,27 @@ class Settings(Base):
|
|||||||
value = self.f_datetime or self.f_int or self.f_string
|
value = self.f_datetime or self.f_int or self.f_string
|
||||||
return f"<Settings(id={self.id}, name={self.name}, {value=}>"
|
return f"<Settings(id={self.id}, name={self.name}, {value=}>"
|
||||||
|
|
||||||
|
def __init__(self, session: scoped_session, name: str):
|
||||||
|
|
||||||
|
self.name = name
|
||||||
|
session.add(self)
|
||||||
|
session.flush()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_int_settings(cls, session: scoped_session, name: str) -> "Settings":
|
def get_int_settings(cls, session: scoped_session,
|
||||||
|
name: str) -> "Settings":
|
||||||
"""Get setting for an integer or return new setting record"""
|
"""Get setting for an integer or return new setting record"""
|
||||||
|
|
||||||
int_setting: Settings
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
int_setting = session.execute(
|
return session.execute(
|
||||||
select(cls)
|
select(cls)
|
||||||
.where(cls.name == name)
|
.where(cls.name == name)
|
||||||
).scalar_one()
|
).scalar_one()
|
||||||
|
|
||||||
except NoResultFound:
|
except NoResultFound:
|
||||||
int_setting = Settings()
|
return Settings(session, name)
|
||||||
int_setting.name = name
|
|
||||||
int_setting.f_int = None
|
|
||||||
session.add(int_setting)
|
|
||||||
|
|
||||||
return int_setting
|
def update(self, session: scoped_session, data: dict):
|
||||||
|
|
||||||
def update(self, session: scoped_session, data: "Settings"):
|
|
||||||
for key, value in data.items():
|
for key, value in data.items():
|
||||||
assert hasattr(self, key)
|
assert hasattr(self, key)
|
||||||
setattr(self, key, value)
|
setattr(self, key, value)
|
||||||
@ -607,9 +618,10 @@ class Tracks(Base):
|
|||||||
path = Column(String(2048), index=False, nullable=False, unique=True)
|
path = 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 = relationship("PlaylistRows", back_populates="track")
|
playlistrows: PlaylistRows = relationship("PlaylistRows",
|
||||||
|
back_populates="track")
|
||||||
playlists = association_proxy("playlistrows", "playlist")
|
playlists = association_proxy("playlistrows", "playlist")
|
||||||
playdates = relationship("Playdates", back_populates="track")
|
playdates: Playdates = relationship("Playdates", back_populates="track")
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (
|
return (
|
||||||
@ -650,7 +662,8 @@ class Tracks(Base):
|
|||||||
return session.execute(select(cls)).scalars().all()
|
return session.execute(select(cls)).scalars().all()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_by_path(cls, session: scoped_session, path: str) -> "Tracks":
|
def get_by_path(cls, session: scoped_session,
|
||||||
|
path: str) -> Optional["Tracks"]:
|
||||||
"""
|
"""
|
||||||
Return track with passed path, or None.
|
Return track with passed path, or None.
|
||||||
"""
|
"""
|
||||||
@ -666,7 +679,8 @@ class Tracks(Base):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def search_artists(cls, session: scoped_session, text: str) -> List["Tracks"]:
|
def search_artists(cls, session: scoped_session,
|
||||||
|
text: str) -> List["Tracks"]:
|
||||||
"""Search case-insenstively for artists containing str"""
|
"""Search case-insenstively for artists containing str"""
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -680,7 +694,8 @@ class Tracks(Base):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def search_titles(cls, session: scoped_session, text: str) -> List["Tracks"]:
|
def search_titles(cls, session: scoped_session,
|
||||||
|
text: str) -> List["Tracks"]:
|
||||||
"""Search case-insenstively for titles containing str"""
|
"""Search case-insenstively for titles containing str"""
|
||||||
return (
|
return (
|
||||||
session.execute(
|
session.execute(
|
||||||
|
|||||||
@ -27,7 +27,7 @@ from PyQt5.QtWidgets import (
|
|||||||
QProgressBar,
|
QProgressBar,
|
||||||
)
|
)
|
||||||
|
|
||||||
from dbconfig import engine, Session
|
from dbconfig import engine, Session, scoped_session
|
||||||
import helpers
|
import helpers
|
||||||
import music
|
import music
|
||||||
|
|
||||||
@ -59,7 +59,8 @@ class CartButton(QPushButton):
|
|||||||
"""Create a cart pushbutton and set it disabled"""
|
"""Create a cart pushbutton and set it disabled"""
|
||||||
|
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.parent = parent
|
# Next line is redundant (check)
|
||||||
|
# self.parent = parent
|
||||||
self.cart_id = cart.id
|
self.cart_id = cart.id
|
||||||
if cart.path and cart.enabled and not cart.duration:
|
if cart.path and cart.enabled and not cart.duration:
|
||||||
tags = helpers.get_tags(cart.path)
|
tags = helpers.get_tags(cart.path)
|
||||||
@ -126,28 +127,28 @@ class PlaylistTrack:
|
|||||||
number: that's the playlist's problem.
|
number: that's the playlist's problem.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.artist = None
|
self.artist: Optional[str] = None
|
||||||
self.duration = None
|
self.duration: Optional[int] = None
|
||||||
self.end_time = None
|
self.end_time: Optional[datetime] = None
|
||||||
self.fade_at = None
|
self.fade_at: Optional[int] = None
|
||||||
self.fade_length = None
|
self.fade_length: Optional[int] = None
|
||||||
self.path = None
|
self.path: Optional[str] = None
|
||||||
self.playlist_id = None
|
self.playlist_id: Optional[int] = None
|
||||||
self.playlist_tab = None
|
self.playlist_tab: Optional[PlaylistTab] = None
|
||||||
self.plr_id = None
|
self.plr_id: Optional[int] = None
|
||||||
self.silence_at = None
|
self.silence_at: Optional[datetime] = None
|
||||||
self.start_gap = None
|
self.start_gap: Optional[int] = None
|
||||||
self.start_time = None
|
self.start_time: Optional[datetime] = None
|
||||||
self.title = None
|
self.title: Optional[str] = None
|
||||||
self.track_id = None
|
self.track_id: Optional[int] = None
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (
|
return (
|
||||||
f"<PlaylistTrack(title={self.title}, artist={self.artist}, "
|
f"<PlaylistTrack(title={self.title}, artist={self.artist}, "
|
||||||
f"row_number={self.row_number} playlist_id={self.playlist_id}>"
|
f"playlist_id={self.playlist_id}>"
|
||||||
)
|
)
|
||||||
|
|
||||||
def set_plr(self, session: Session, plr: PlaylistRows,
|
def set_plr(self, session: scoped_session, plr: PlaylistRows,
|
||||||
tab: PlaylistTab) -> None:
|
tab: PlaylistTab) -> None:
|
||||||
"""
|
"""
|
||||||
Update with new plr information
|
Update with new plr information
|
||||||
@ -492,7 +493,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
self.timer.timeout.connect(self.tick)
|
self.timer.timeout.connect(self.tick)
|
||||||
|
|
||||||
def create_playlist(self,
|
def create_playlist(self,
|
||||||
session: Session,
|
session: scoped_session,
|
||||||
playlist_name: Optional[str] = None) -> Playlists:
|
playlist_name: Optional[str] = None) -> Playlists:
|
||||||
"""Create new playlist"""
|
"""Create new playlist"""
|
||||||
|
|
||||||
@ -512,7 +513,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
if playlist:
|
if playlist:
|
||||||
self.create_playlist_tab(session, playlist)
|
self.create_playlist_tab(session, playlist)
|
||||||
|
|
||||||
def create_playlist_tab(self, session: Session,
|
def create_playlist_tab(self, session: scoped_session,
|
||||||
playlist: Playlists) -> int:
|
playlist: Playlists) -> int:
|
||||||
"""
|
"""
|
||||||
Take the passed playlist database object, create a playlist tab and
|
Take the passed playlist database object, create a playlist tab and
|
||||||
@ -714,7 +715,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
QMessageBox.Ok
|
QMessageBox.Ok
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_one_track(self, session: Session) -> Optional[Tracks]:
|
def get_one_track(self, session: scoped_session) -> Optional[Tracks]:
|
||||||
"""Show dialog box to select one track and return it to caller"""
|
"""Show dialog box to select one track and return it to caller"""
|
||||||
|
|
||||||
dlg = DbDialog(self, session, get_one_track=True)
|
dlg = DbDialog(self, session, get_one_track=True)
|
||||||
@ -833,10 +834,10 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
_ = self.create_playlist_tab(session, playlist)
|
_ = self.create_playlist_tab(session, playlist)
|
||||||
# Set active tab
|
# Set active tab
|
||||||
record = Settings.get_int_settings(session, "active_tab")
|
record = Settings.get_int_settings(session, "active_tab")
|
||||||
if record and record.f_int is not None:
|
if record and record.f_int >= 0:
|
||||||
self.tabPlaylist.setCurrentIndex(record.f_int)
|
self.tabPlaylist.setCurrentIndex(record.f_int)
|
||||||
|
|
||||||
def move_playlist_rows(self, session: Session,
|
def move_playlist_rows(self, session: scoped_session,
|
||||||
playlistrows: List[PlaylistRows]) -> None:
|
playlistrows: List[PlaylistRows]) -> None:
|
||||||
"""
|
"""
|
||||||
Move passed playlist rows to another playlist
|
Move passed playlist rows to another playlist
|
||||||
@ -1325,7 +1326,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
# May also be called when last tab is closed
|
# May also be called when last tab is closed
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def this_is_the_next_playlist_row(self, session: Session,
|
def this_is_the_next_playlist_row(self, session: scoped_session,
|
||||||
plr: PlaylistRows,
|
plr: PlaylistRows,
|
||||||
playlist_tab: PlaylistTab) -> None:
|
playlist_tab: PlaylistTab) -> None:
|
||||||
"""
|
"""
|
||||||
@ -1468,6 +1469,9 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
# Time to end
|
# Time to end
|
||||||
self.label_end_timer.setText(helpers.ms_to_mmss(time_to_end))
|
self.label_end_timer.setText(helpers.ms_to_mmss(time_to_end))
|
||||||
|
|
||||||
|
# Autoplay next track
|
||||||
|
# if time_to_silence <= 1500:
|
||||||
|
# self.play_next()
|
||||||
else:
|
else:
|
||||||
if self.playing:
|
if self.playing:
|
||||||
self.stop_playing()
|
self.stop_playing()
|
||||||
@ -1500,7 +1504,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
class CartDialog(QDialog):
|
class CartDialog(QDialog):
|
||||||
"""Edit cart details"""
|
"""Edit cart details"""
|
||||||
|
|
||||||
def __init__(self, parent: QMainWindow, session: Session,
|
def __init__(self, parent: QMainWindow, session: scoped_session,
|
||||||
cart: Carts) -> None:
|
cart: Carts) -> None:
|
||||||
"""
|
"""
|
||||||
Manage carts
|
Manage carts
|
||||||
@ -1537,7 +1541,7 @@ class CartDialog(QDialog):
|
|||||||
class DbDialog(QDialog):
|
class DbDialog(QDialog):
|
||||||
"""Select track from database"""
|
"""Select track from database"""
|
||||||
|
|
||||||
def __init__(self, parent: QMainWindow, session: Session,
|
def __init__(self, parent: QMainWindow, session: scoped_session,
|
||||||
get_one_track: bool = False) -> None:
|
get_one_track: bool = False) -> None:
|
||||||
"""
|
"""
|
||||||
Subclassed QDialog to manage track selection
|
Subclassed QDialog to manage track selection
|
||||||
@ -1762,10 +1766,12 @@ if __name__ == "__main__":
|
|||||||
log.debug("Updating database")
|
log.debug("Updating database")
|
||||||
with Session() as session:
|
with Session() as session:
|
||||||
check_db(session)
|
check_db(session)
|
||||||
|
engine.dispose()
|
||||||
elif args.update_bitrates:
|
elif args.update_bitrates:
|
||||||
log.debug("Update bitrates")
|
log.debug("Update bitrates")
|
||||||
with Session() as session:
|
with Session() as session:
|
||||||
update_bitrates(session)
|
update_bitrates(session)
|
||||||
|
engine.dispose()
|
||||||
else:
|
else:
|
||||||
# Normal run
|
# Normal run
|
||||||
try:
|
try:
|
||||||
@ -1773,7 +1779,9 @@ if __name__ == "__main__":
|
|||||||
app = QApplication(sys.argv)
|
app = QApplication(sys.argv)
|
||||||
win = Window()
|
win = Window()
|
||||||
win.show()
|
win.show()
|
||||||
sys.exit(app.exec())
|
status = app.exec()
|
||||||
|
engine.dispose()
|
||||||
|
sys.exit(status)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
from helpers import send_mail
|
from helpers import send_mail
|
||||||
|
|
||||||
|
|||||||
@ -435,7 +435,6 @@ class PlaylistTab(QTableWidget):
|
|||||||
track = session.get(Tracks, track_id)
|
track = session.get(Tracks, track_id)
|
||||||
if track:
|
if track:
|
||||||
if self.edit_cell_type == TITLE:
|
if self.edit_cell_type == TITLE:
|
||||||
log.debug(f"KAE: _cell_changed:440, {new_text=}")
|
|
||||||
track.title = new_text
|
track.title = new_text
|
||||||
if update_current:
|
if update_current:
|
||||||
self.musicmuster.current_track.title = new_text
|
self.musicmuster.current_track.title = new_text
|
||||||
@ -608,7 +607,6 @@ class PlaylistTab(QTableWidget):
|
|||||||
self.setItem(row, START_GAP, start_gap_item)
|
self.setItem(row, START_GAP, start_gap_item)
|
||||||
|
|
||||||
title_item = QTableWidgetItem(plr.track.title)
|
title_item = QTableWidgetItem(plr.track.title)
|
||||||
log.debug(f"KAE: insert_row:619, {title_item.text()=}")
|
|
||||||
self.setItem(row, TITLE, title_item)
|
self.setItem(row, TITLE, title_item)
|
||||||
|
|
||||||
artist_item = QTableWidgetItem(plr.track.artist)
|
artist_item = QTableWidgetItem(plr.track.artist)
|
||||||
@ -667,7 +665,8 @@ class PlaylistTab(QTableWidget):
|
|||||||
userdata_item.setData(self.ROW_TRACK_ID, 0)
|
userdata_item.setData(self.ROW_TRACK_ID, 0)
|
||||||
|
|
||||||
if repaint:
|
if repaint:
|
||||||
self.update_display(session)
|
# Schedule so that display can update with new row first
|
||||||
|
QTimer.singleShot(0, lambda: self.update_display(session))
|
||||||
|
|
||||||
def insert_track(self, session: scoped_session, track: Tracks,
|
def insert_track(self, session: scoped_session, track: Tracks,
|
||||||
note: Optional[str] = None, repaint: bool = True) -> None:
|
note: Optional[str] = None, repaint: bool = True) -> None:
|
||||||
@ -1206,7 +1205,7 @@ class PlaylistTab(QTableWidget):
|
|||||||
|
|
||||||
return start + timedelta(milliseconds=duration)
|
return start + timedelta(milliseconds=duration)
|
||||||
|
|
||||||
def _column_resize(self, idx: int, old: int, new: int) -> None:
|
def _column_resize(self, idx: int, _old: int, _new: int) -> None:
|
||||||
"""
|
"""
|
||||||
Called when column widths are changed.
|
Called when column widths are changed.
|
||||||
|
|
||||||
@ -1361,7 +1360,7 @@ class PlaylistTab(QTableWidget):
|
|||||||
return playlistrow_id
|
return playlistrow_id
|
||||||
|
|
||||||
def _get_playlistrow_object(self, session: scoped_session,
|
def _get_playlistrow_object(self, session: scoped_session,
|
||||||
row: int) -> int:
|
row: int) -> PlaylistRows:
|
||||||
"""Return the playlistrow object associated with this row"""
|
"""Return the playlistrow object associated with this row"""
|
||||||
|
|
||||||
playlistrow_id = (self.item(row, USERDATA).data(self.PLAYLISTROW_ID))
|
playlistrow_id = (self.item(row, USERDATA).data(self.PLAYLISTROW_ID))
|
||||||
@ -1490,48 +1489,6 @@ class PlaylistTab(QTableWidget):
|
|||||||
and pos.y() >= rect.center().y() # noqa W503
|
and pos.y() >= rect.center().y() # noqa W503
|
||||||
)
|
)
|
||||||
|
|
||||||
def _meta_clear_attribute(self, row: int, attribute: int) -> None:
|
|
||||||
"""Clear given metadata for row"""
|
|
||||||
|
|
||||||
if row is None:
|
|
||||||
raise ValueError(f"_meta_clear_attribute({row=}, {attribute=})")
|
|
||||||
|
|
||||||
new_metadata: int = self._meta_get(row) & ~(1 << attribute)
|
|
||||||
self.item(row, USERDATA).setData(self.ROW_FLAGS, new_metadata)
|
|
||||||
|
|
||||||
def _meta_get(self, row: int) -> int:
|
|
||||||
"""Return row metadata"""
|
|
||||||
|
|
||||||
return (self.item(row, USERDATA).data(self.ROW_FLAGS))
|
|
||||||
|
|
||||||
def _meta_search(self, metadata: int, one: bool = True) -> List[int]:
|
|
||||||
"""
|
|
||||||
Search rows for metadata.
|
|
||||||
|
|
||||||
If one is True, check that only one row matches and return
|
|
||||||
the row number.
|
|
||||||
|
|
||||||
If one is False, return a list of matching row numbers.
|
|
||||||
"""
|
|
||||||
|
|
||||||
matches = []
|
|
||||||
for row in range(self.rowCount()):
|
|
||||||
if self._meta_get(row):
|
|
||||||
if self._meta_get(row) & (1 << metadata):
|
|
||||||
matches.append(row)
|
|
||||||
|
|
||||||
if not one:
|
|
||||||
return matches
|
|
||||||
|
|
||||||
if len(matches) <= 1:
|
|
||||||
return matches
|
|
||||||
else:
|
|
||||||
log.error(
|
|
||||||
f"Multiple matches for metadata '{metadata}' found "
|
|
||||||
f"in rows: {', '.join([str(x) for x in matches])}"
|
|
||||||
)
|
|
||||||
raise AttributeError(f"Multiple '{metadata}' metadata {matches}")
|
|
||||||
|
|
||||||
def _move_row(self, session: scoped_session, plr: PlaylistRows,
|
def _move_row(self, session: scoped_session, plr: PlaylistRows,
|
||||||
new_row_number: int) -> None:
|
new_row_number: int) -> None:
|
||||||
"""Move playlist row to new_row_number using parent copy/paste"""
|
"""Move playlist row to new_row_number using parent copy/paste"""
|
||||||
@ -1765,7 +1722,7 @@ class PlaylistTab(QTableWidget):
|
|||||||
continue
|
continue
|
||||||
attr_name = f"playlist_{column_name}_col_width"
|
attr_name = f"playlist_{column_name}_col_width"
|
||||||
record: Settings = Settings.get_int_settings(session, attr_name)
|
record: Settings = Settings.get_int_settings(session, attr_name)
|
||||||
if record and record.f_int is not None:
|
if record and record.f_int >= 0:
|
||||||
self.setColumnWidth(idx, record.f_int)
|
self.setColumnWidth(idx, record.f_int)
|
||||||
else:
|
else:
|
||||||
self.setColumnWidth(idx, Config.DEFAULT_COLUMN_WIDTH)
|
self.setColumnWidth(idx, Config.DEFAULT_COLUMN_WIDTH)
|
||||||
@ -1933,7 +1890,6 @@ class PlaylistTab(QTableWidget):
|
|||||||
item_startgap.setBackground(QColor("white"))
|
item_startgap.setBackground(QColor("white"))
|
||||||
|
|
||||||
item_title = self.item(row, TITLE)
|
item_title = self.item(row, TITLE)
|
||||||
log.debug(f"KAE: _update_row:1978, {track.title=}")
|
|
||||||
item_title.setText(track.title)
|
item_title.setText(track.title)
|
||||||
|
|
||||||
item_artist = self.item(row, ARTIST)
|
item_artist = self.item(row, ARTIST)
|
||||||
|
|||||||
@ -35,9 +35,9 @@ parent_dir = os.path.dirname(source_dir)
|
|||||||
|
|
||||||
name_and_tags: List[str] = []
|
name_and_tags: List[str] = []
|
||||||
tags_not_name: List[str] = []
|
tags_not_name: List[str] = []
|
||||||
multiple_similar: List[str] = []
|
# multiple_similar: List[str] = []
|
||||||
no_match: List[str] = []
|
no_match: List[str] = []
|
||||||
possibles: List[str] = []
|
# possibles: List[str] = []
|
||||||
no_match: int = 0
|
no_match: int = 0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,17 +0,0 @@
|
|||||||
from PyQt5.QtCore import Qt
|
|
||||||
from PyQt5.QtGui import QFontMetrics, QPainter
|
|
||||||
from PyQt5.QtWidgets import QLabel
|
|
||||||
|
|
||||||
|
|
||||||
# class ElideLabel(QLabel):
|
|
||||||
# """
|
|
||||||
# From https://stackoverflow.com/questions/11446478/
|
|
||||||
# pyside-pyqt-truncate-text-in-qlabel-based-on-minimumsize
|
|
||||||
# """
|
|
||||||
#
|
|
||||||
# def paintEvent(self, event):
|
|
||||||
# painter = QPainter(self)
|
|
||||||
# metrics = QFontMetrics(self.font())
|
|
||||||
# elided = metrics.elidedText(self.text(), Qt.ElideRight, self.width())
|
|
||||||
#
|
|
||||||
# painter.drawText(self.rect(), self.alignment(), elided)
|
|
||||||
@ -70,11 +70,4 @@ def do_command(command):
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
def quick_test():
|
|
||||||
"""Example list of commands."""
|
|
||||||
do_command('Help: Command=Help')
|
|
||||||
do_command('Help: Command="GetInfo"')
|
|
||||||
# do_command('SetPreference: Name=GUI/Theme Value=classic Reload=1')
|
|
||||||
|
|
||||||
|
|
||||||
do_command('Import2: Filename=/home/kae/git/musicmuster/archive/boot.flac')
|
do_command('Import2: Filename=/home/kae/git/musicmuster/archive/boot.flac')
|
||||||
|
|||||||
94
play.py
94
play.py
@ -1,94 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
from sqlalchemy import create_engine
|
|
||||||
from sqlalchemy import text
|
|
||||||
from sqlalchemy import Table, Column, Integer, String
|
|
||||||
from sqlalchemy import ForeignKey
|
|
||||||
from sqlalchemy import select
|
|
||||||
from sqlalchemy import insert
|
|
||||||
from sqlalchemy.orm import Session
|
|
||||||
from sqlalchemy.orm import declarative_base
|
|
||||||
from sqlalchemy.orm import relationship
|
|
||||||
|
|
||||||
|
|
||||||
Base = declarative_base()
|
|
||||||
|
|
||||||
engine = create_engine("sqlite+pysqlite:///:memory:", echo=True, future=True)
|
|
||||||
|
|
||||||
|
|
||||||
class User(Base):
|
|
||||||
__tablename__ = 'user_account'
|
|
||||||
id = Column(Integer, primary_key=True)
|
|
||||||
name = Column(String(30))
|
|
||||||
fullname = Column(String)
|
|
||||||
addresses = relationship("Address", back_populates="user")
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return (
|
|
||||||
f"User(id={self.id!r}, name={self.name!r}, "
|
|
||||||
f"fullname={self.fullname!r})"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Address(Base):
|
|
||||||
__tablename__ = 'address'
|
|
||||||
id = Column(Integer, primary_key=True)
|
|
||||||
email_address = Column(String, nullable=False)
|
|
||||||
user_id = Column(Integer, ForeignKey('user_account.id'))
|
|
||||||
user = relationship("User", back_populates="addresses")
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f"Address(id={self.id!r}, email_address={self.email_address!r})"
|
|
||||||
|
|
||||||
|
|
||||||
Base.metadata.create_all(engine)
|
|
||||||
|
|
||||||
squidward = User(name="squidward", fullname="Squidward Tentacles")
|
|
||||||
krabs = User(name="ehkrabs", fullname="Eugene H. Krabs")
|
|
||||||
|
|
||||||
session = Session(engine)
|
|
||||||
|
|
||||||
session.add(squidward)
|
|
||||||
session.add(krabs)
|
|
||||||
|
|
||||||
session.commit()
|
|
||||||
|
|
||||||
u1 = User(name='pkrabs', fullname='Pearl Krabs')
|
|
||||||
a1 = Address(email_address="pearl.krabs@gmail.com")
|
|
||||||
u1.addresses.append(a1)
|
|
||||||
a2 = Address(email_address="pearl@aol.com", user=u1)
|
|
||||||
|
|
||||||
session.add(u1)
|
|
||||||
session.add(a1)
|
|
||||||
session.add(a2)
|
|
||||||
|
|
||||||
session.commit()
|
|
||||||
|
|
||||||
|
|
||||||
# with engine.connect() as conn:
|
|
||||||
# conn.execute(text("CREATE TABLE some_table (x int, y int)"))
|
|
||||||
# conn.execute(
|
|
||||||
# text("INSERT INTO some_table (x, y) VALUES (:x, :y)"),
|
|
||||||
# [{"x": 1, "y": 1}, {"x": 2, "y": 4}]
|
|
||||||
# )
|
|
||||||
# conn.commit()
|
|
||||||
#
|
|
||||||
# with engine.begin() as conn:
|
|
||||||
# conn.execute(
|
|
||||||
# text("INSERT INTO some_table (x, y) VALUES (:x, :y)"),
|
|
||||||
# [{"x": 6, "y": 8}, {"x": 9, "y": 10}]
|
|
||||||
# )
|
|
||||||
#
|
|
||||||
# # with engine.connect() as conn:
|
|
||||||
# # result = conn.execute(text("SELECT x, y FROM some_table"))
|
|
||||||
# # for row in result:
|
|
||||||
# # print(f"x: {row.x} y: {row.y}")
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# stmt = text(
|
|
||||||
# "SELECT x, y FROM some_table WHERE y > :y ORDER BY x, y").bindparams(y=6)
|
|
||||||
#
|
|
||||||
# with Session(engine) as session:
|
|
||||||
# result = session.execute(stmt)
|
|
||||||
# for row in result:
|
|
||||||
# print(f"x: {row.x} y: {row.y}")
|
|
||||||
30
poetry.lock
generated
30
poetry.lock
generated
@ -306,7 +306,7 @@ python-versions = ">=3.7"
|
|||||||
name = "mypy"
|
name = "mypy"
|
||||||
version = "0.991"
|
version = "0.991"
|
||||||
description = "Optional static typing for Python"
|
description = "Optional static typing for Python"
|
||||||
category = "main"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
@ -325,7 +325,7 @@ reports = ["lxml"]
|
|||||||
name = "mypy-extensions"
|
name = "mypy-extensions"
|
||||||
version = "0.4.3"
|
version = "0.4.3"
|
||||||
description = "Experimental type system extensions for programs checked with the mypy typechecker."
|
description = "Experimental type system extensions for programs checked with the mypy typechecker."
|
||||||
category = "main"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
|
|
||||||
@ -671,23 +671,11 @@ postgresql_psycopg2cffi = ["psycopg2cffi"]
|
|||||||
pymysql = ["pymysql (<1)", "pymysql"]
|
pymysql = ["pymysql (<1)", "pymysql"]
|
||||||
sqlcipher = ["sqlcipher3-binary"]
|
sqlcipher = ["sqlcipher3-binary"]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sqlalchemy-stubs"
|
|
||||||
version = "0.4"
|
|
||||||
description = "SQLAlchemy stubs and mypy plugin"
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = "*"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
mypy = ">=0.790"
|
|
||||||
typing-extensions = ">=3.7.4"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sqlalchemy2-stubs"
|
name = "sqlalchemy2-stubs"
|
||||||
version = "0.0.2a31"
|
version = "0.0.2a32"
|
||||||
description = "Typing Stubs for SQLAlchemy 1.4"
|
description = "Typing Stubs for SQLAlchemy 1.4"
|
||||||
category = "main"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.6"
|
python-versions = ">=3.6"
|
||||||
|
|
||||||
@ -760,7 +748,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
|||||||
name = "tomli"
|
name = "tomli"
|
||||||
version = "2.0.1"
|
version = "2.0.1"
|
||||||
description = "A lil' TOML parser"
|
description = "A lil' TOML parser"
|
||||||
category = "main"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
@ -788,7 +776,7 @@ python-versions = "*"
|
|||||||
name = "typing-extensions"
|
name = "typing-extensions"
|
||||||
version = "4.4.0"
|
version = "4.4.0"
|
||||||
description = "Backported and Experimental Type Hints for Python 3.7+"
|
description = "Backported and Experimental Type Hints for Python 3.7+"
|
||||||
category = "main"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
@ -816,7 +804,7 @@ python-versions = "*"
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "1.1"
|
lock-version = "1.1"
|
||||||
python-versions = "^3.9"
|
python-versions = "^3.9"
|
||||||
content-hash = "9ae8194c057d5eb51e7f65f5328f94ea85e0a48a5fe12d38ad72f4a1aae7cbca"
|
content-hash = "389d73715056202a39f0efe23941943d7a6849915607cb8ceb4c77bde7e4f709"
|
||||||
|
|
||||||
[metadata.files]
|
[metadata.files]
|
||||||
alembic = []
|
alembic = []
|
||||||
@ -922,10 +910,6 @@ six = [
|
|||||||
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
|
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
|
||||||
]
|
]
|
||||||
sqlalchemy = []
|
sqlalchemy = []
|
||||||
sqlalchemy-stubs = [
|
|
||||||
{file = "sqlalchemy-stubs-0.4.tar.gz", hash = "sha256:c665d6dd4482ef642f01027fa06c3d5e91befabb219dc71fc2a09e7d7695f7ae"},
|
|
||||||
{file = "sqlalchemy_stubs-0.4-py3-none-any.whl", hash = "sha256:5eec7aa110adf9b957b631799a72fef396b23ff99fe296df726645d01e312aa5"},
|
|
||||||
]
|
|
||||||
sqlalchemy2-stubs = []
|
sqlalchemy2-stubs = []
|
||||||
stack-data = []
|
stack-data = []
|
||||||
stackprinter = []
|
stackprinter = []
|
||||||
|
|||||||
@ -24,18 +24,17 @@ python-Levenshtein = "^0.12.2"
|
|||||||
pyfzf = "^0.3.1"
|
pyfzf = "^0.3.1"
|
||||||
pydymenu = "^0.5.2"
|
pydymenu = "^0.5.2"
|
||||||
stackprinter = "^0.2.10"
|
stackprinter = "^0.2.10"
|
||||||
sqlalchemy-stubs = "^0.4"
|
|
||||||
sqlalchemy2-stubs = "^0.0.2-alpha.31"
|
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.dev-dependencies]
|
||||||
ipdb = "^0.13.9"
|
ipdb = "^0.13.9"
|
||||||
sqlalchemy-stubs = "^0.4"
|
|
||||||
PyQt5-stubs = "^5.15.2"
|
PyQt5-stubs = "^5.15.2"
|
||||||
pytest = "^7.0.1"
|
pytest = "^7.0.1"
|
||||||
pytest-qt = "^4.0.2"
|
pytest-qt = "^4.0.2"
|
||||||
pydub-stubs = "^0.25.1"
|
pydub-stubs = "^0.25.1"
|
||||||
line-profiler = "^4.0.2"
|
line-profiler = "^4.0.2"
|
||||||
flakehell = "^0.9.0"
|
flakehell = "^0.9.0"
|
||||||
|
sqlalchemy2-stubs = "^0.0.2-alpha.32"
|
||||||
|
mypy = "^0.991"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core>=1.0.0"]
|
requires = ["poetry-core>=1.0.0"]
|
||||||
@ -45,3 +44,7 @@ build-backend = "poetry.core.masonry.api"
|
|||||||
mypy_path = "/home/kae/.cache/pypoetry/virtualenvs/musicmuster-oWgGw1IG-py3.9:/home/kae/git/musicmuster/app"
|
mypy_path = "/home/kae/.cache/pypoetry/virtualenvs/musicmuster-oWgGw1IG-py3.9:/home/kae/git/musicmuster/app"
|
||||||
plugins = "sqlalchemy.ext.mypy.plugin"
|
plugins = "sqlalchemy.ext.mypy.plugin"
|
||||||
|
|
||||||
|
[tool.vulture]
|
||||||
|
exclude = ["migrations", "app/ui", "archive"]
|
||||||
|
paths = ["app"]
|
||||||
|
make_whitelist = true
|
||||||
|
|||||||
16
test.py
16
test.py
@ -2,28 +2,28 @@
|
|||||||
|
|
||||||
from PyQt5 import QtGui, QtWidgets
|
from PyQt5 import QtGui, QtWidgets
|
||||||
|
|
||||||
class TabBar(QtWidgets.QTabWidget):
|
class TabBar(QtWidgets.QTabBar):
|
||||||
def paintEvent(self, event):
|
def paintEvent(self, event):
|
||||||
painter = QtGui.QStylePainter(self)
|
painter = QtWidgets.QStylePainter(self)
|
||||||
option = QtGui.QStyleOptionTab()
|
option = QtWidgets.QStyleOptionTab()
|
||||||
for index in range(self.count()):
|
for index in range(self.count()):
|
||||||
self.initStyleOption(option, index)
|
self.initStyleOption(option, index)
|
||||||
bgcolor = QtGui.QColor(self.tabText(index))
|
bgcolor = QtGui.QColor(self.tabText(index))
|
||||||
option.palette.setColor(QtGui.QPalette.Window, bgcolor)
|
option.palette.setColor(QtGui.QPalette.Window, bgcolor)
|
||||||
painter.drawControl(QtGui.QStyle.CE_TabBarTabShape, option)
|
painter.drawControl(QtWidgets.QStyle.CE_TabBarTabShape, option)
|
||||||
painter.drawControl(QtGui.QStyle.CE_TabBarTabLabel, option)
|
painter.drawControl(QtWidgets.QStyle.CE_TabBarTabLabel, option)
|
||||||
|
|
||||||
class Window(QtWidgets.QTabWidget):
|
class Window(QtWidgets.QTabWidget):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
QtGui.QTabWidget.__init__(self)
|
QtWidgets.QTabWidget.__init__(self)
|
||||||
self.setTabBar(TabBar(self))
|
self.setTabBar(TabBar(self))
|
||||||
for color in 'tomato orange yellow lightgreen skyblue plum'.split():
|
for color in 'tomato orange yellow lightgreen skyblue plum'.split():
|
||||||
self.addTab(QtGui.QWidget(self), color)
|
self.addTab(QtWidgets.QWidget(self), color)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
app = QtGui.QApplication(sys.argv)
|
app = QtWidgets.QApplication(sys.argv)
|
||||||
window = Window()
|
window = Window()
|
||||||
window.resize(420, 200)
|
window.resize(420, 200)
|
||||||
window.show()
|
window.show()
|
||||||
|
|||||||
@ -5,7 +5,6 @@ from app.models import (
|
|||||||
Notes,
|
Notes,
|
||||||
Playdates,
|
Playdates,
|
||||||
Playlists,
|
Playlists,
|
||||||
PlaylistTracks,
|
|
||||||
Tracks,
|
Tracks,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -205,11 +204,11 @@ def test_playlist_notes(session):
|
|||||||
# We need two notes
|
# We need two notes
|
||||||
note1_text = "note1 text"
|
note1_text = "note1 text"
|
||||||
note1_row = 11
|
note1_row = 11
|
||||||
note1 = Notes(session, playlist.id, note1_row, note1_text)
|
_ = Notes(session, playlist.id, note1_row, note1_text)
|
||||||
|
|
||||||
note2_text = "note2 text"
|
note2_text = "note2 text"
|
||||||
note2_row = 19
|
note2_row = 19
|
||||||
note2 = Notes(session, playlist.id, note2_row, note2_text)
|
_ = Notes(session, playlist.id, note2_row, note2_text)
|
||||||
|
|
||||||
notes = playlist.notes
|
notes = playlist.notes
|
||||||
assert note1_text in [n.note for n in notes]
|
assert note1_text in [n.note for n in notes]
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user