Compare commits

..

No commits in common. "f182f49f15df2a8dbdb85f192e8846e2b44db1c3" and "dfb9326d5eea318fccbfde4725cec4c05c64b7d6" have entirely different histories.

11 changed files with 204 additions and 104 deletions

View File

@ -5,17 +5,21 @@ import smtplib
import ssl import ssl
import tempfile import tempfile
from config import Config
from datetime import datetime
from email.message import EmailMessage from email.message import EmailMessage
from log import log
from mutagen.flac import FLAC # type: ignore from mutagen.flac import FLAC # type: ignore
from mutagen.mp3 import MP3 # type: ignore from mutagen.mp3 import MP3 # type: ignore
from pydub import AudioSegment, effects from pydub import effects
from pydub.utils import mediainfo from pydub.utils import mediainfo
from config import Config
from datetime import datetime
from log import log
from pydub import AudioSegment
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 Any, Dict, Optional, Union from typing import Optional
# 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:
@ -78,7 +82,7 @@ def get_audio_segment(path: str) -> Optional[AudioSegment]:
return None return None
def get_tags(path: str) -> Dict[str, Any]: def get_tags(path: str) -> Dict[str, Union[str, int]]:
""" """
Return a dictionary of title, artist, duration-in-milliseconds and path. Return a dictionary of title, artist, duration-in-milliseconds and path.
""" """
@ -94,7 +98,7 @@ def get_tags(path: str) -> Dict[str, Any]:
) )
def get_relative_date(past_date: Optional[datetime], def get_relative_date(past_date: 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.
@ -185,8 +189,7 @@ def send_mail(to_addr, from_addr, subj, body):
s.quit() s.quit()
def ms_to_mmss(ms: Optional[int], decimals: int = 0, def ms_to_mmss(ms: int, decimals: int = 0, negative: bool = False) -> str:
negative: bool = False) -> str:
"""Convert milliseconds to mm:ss""" """Convert milliseconds to mm:ss"""
minutes: int minutes: int

View File

@ -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: Optional[int] = None, path: Optional[str] = None, duration: int = None, path: 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: "Tracks" = relationship("Tracks", back_populates="playdates") track = relationship("Tracks", back_populates="playdates")
def __repr__(self) -> str: def __repr__(self) -> str:
return ( return (
@ -149,8 +149,7 @@ class Playdates(Base):
session.commit() session.commit()
@staticmethod @staticmethod
def last_played(session: scoped_session, def last_played(session: scoped_session, track_id: int) -> Optional[datetime]:
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(
@ -166,8 +165,7 @@ class Playdates(Base):
return None return None
@staticmethod @staticmethod
def played_after(session: scoped_session, def played_after(session: scoped_session, since: datetime) -> List["Playdates"]:
since: datetime) -> List["Playdates"]:
"""Return a list of Playdates objects since passed time""" """Return a list of Playdates objects since passed time"""
return ( return (
@ -188,7 +186,7 @@ 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)
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)
@ -196,7 +194,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: "PlaylistRows" = relationship( rows = relationship(
"PlaylistRows", "PlaylistRows",
back_populates="playlist", back_populates="playlist",
cascade="all, delete-orphan", cascade="all, delete-orphan",
@ -209,10 +207,10 @@ class Playlists(Base):
f"is_templatee={self.is_template}>" f"is_templatee={self.is_template}>"
) )
def __init__(self, session: scoped_session, name: str): def __init__(self, session: scoped_session, name: str) -> None:
self.name = name self.name = name
session.add(self) session.add(self)
session.flush() session.commit()
def close(self, session: scoped_session) -> None: def close(self, session: scoped_session) -> None:
"""Mark playlist as unloaded""" """Mark playlist as unloaded"""
@ -233,15 +231,10 @@ class Playlists(Base):
session: scoped_session, session: scoped_session,
template: "Playlists", template: "Playlists",
playlist_name: str) \ playlist_name: str) \
-> Optional["Playlists"]: -> "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
@ -339,9 +332,6 @@ 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()
@ -355,9 +345,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: Playlists = relationship(Playlists, back_populates="rows") playlist = 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 = 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:
@ -458,8 +448,7 @@ class PlaylistRows(Base):
).first() ).first()
@staticmethod @staticmethod
def get_last_used_row(session: scoped_session, def get_last_used_row(session: scoped_session, playlist_id: int) -> Optional[int]:
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(
@ -526,8 +515,8 @@ class PlaylistRows(Base):
return plrs return plrs
@staticmethod @staticmethod
def move_rows_down(session: scoped_session, playlist_id: int, def move_rows_down(session: scoped_session, playlist_id: int, starting_row: int,
starting_row: int, move_by: int) -> None: 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
@ -578,27 +567,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, def get_int_settings(cls, session: scoped_session, name: str) -> "Settings":
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:
return session.execute( int_setting = session.execute(
select(cls) select(cls)
.where(cls.name == name) .where(cls.name == name)
).scalar_one() ).scalar_one()
except NoResultFound: except NoResultFound:
return Settings(session, name) int_setting = Settings()
int_setting.name = name
int_setting.f_int = None
session.add(int_setting)
def update(self, session: scoped_session, data: dict): return int_setting
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)
@ -618,10 +607,9 @@ 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: PlaylistRows = relationship("PlaylistRows", playlistrows = relationship("PlaylistRows", back_populates="track")
back_populates="track")
playlists = association_proxy("playlistrows", "playlist") playlists = association_proxy("playlistrows", "playlist")
playdates: Playdates = relationship("Playdates", back_populates="track") playdates = relationship("Playdates", back_populates="track")
def __repr__(self) -> str: def __repr__(self) -> str:
return ( return (
@ -662,8 +650,7 @@ 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, def get_by_path(cls, session: scoped_session, path: str) -> "Tracks":
path: str) -> Optional["Tracks"]:
""" """
Return track with passed path, or None. Return track with passed path, or None.
""" """
@ -679,8 +666,7 @@ class Tracks(Base):
return None return None
@classmethod @classmethod
def search_artists(cls, session: scoped_session, def search_artists(cls, session: scoped_session, text: str) -> List["Tracks"]:
text: str) -> List["Tracks"]:
"""Search case-insenstively for artists containing str""" """Search case-insenstively for artists containing str"""
return ( return (
@ -694,8 +680,7 @@ class Tracks(Base):
) )
@classmethod @classmethod
def search_titles(cls, session: scoped_session, def search_titles(cls, session: scoped_session, text: str) -> List["Tracks"]:
text: str) -> List["Tracks"]:
"""Search case-insenstively for titles containing str""" """Search case-insenstively for titles containing str"""
return ( return (
session.execute( session.execute(

View File

@ -27,7 +27,7 @@ from PyQt5.QtWidgets import (
QProgressBar, QProgressBar,
) )
from dbconfig import engine, Session, scoped_session from dbconfig import engine, Session
import helpers import helpers
import music import music
@ -59,8 +59,7 @@ 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)
# Next line is redundant (check) self.parent = parent
# 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)
@ -127,28 +126,28 @@ class PlaylistTrack:
number: that's the playlist's problem. number: that's the playlist's problem.
""" """
self.artist: Optional[str] = None self.artist = None
self.duration: Optional[int] = None self.duration = None
self.end_time: Optional[datetime] = None self.end_time = None
self.fade_at: Optional[int] = None self.fade_at = None
self.fade_length: Optional[int] = None self.fade_length = None
self.path: Optional[str] = None self.path = None
self.playlist_id: Optional[int] = None self.playlist_id = None
self.playlist_tab: Optional[PlaylistTab] = None self.playlist_tab = None
self.plr_id: Optional[int] = None self.plr_id = None
self.silence_at: Optional[datetime] = None self.silence_at = None
self.start_gap: Optional[int] = None self.start_gap = None
self.start_time: Optional[datetime] = None self.start_time = None
self.title: Optional[str] = None self.title = None
self.track_id: Optional[int] = None self.track_id = 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"playlist_id={self.playlist_id}>" f"row_number={self.row_number} playlist_id={self.playlist_id}>"
) )
def set_plr(self, session: scoped_session, plr: PlaylistRows, def set_plr(self, session: Session, plr: PlaylistRows,
tab: PlaylistTab) -> None: tab: PlaylistTab) -> None:
""" """
Update with new plr information Update with new plr information
@ -493,7 +492,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: scoped_session, session: Session,
playlist_name: Optional[str] = None) -> Playlists: playlist_name: Optional[str] = None) -> Playlists:
"""Create new playlist""" """Create new playlist"""
@ -513,7 +512,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: scoped_session, def create_playlist_tab(self, session: 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
@ -715,7 +714,7 @@ class Window(QMainWindow, Ui_MainWindow):
QMessageBox.Ok QMessageBox.Ok
) )
def get_one_track(self, session: scoped_session) -> Optional[Tracks]: def get_one_track(self, session: 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)
@ -834,10 +833,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 >= 0: if record and record.f_int is not None:
self.tabPlaylist.setCurrentIndex(record.f_int) self.tabPlaylist.setCurrentIndex(record.f_int)
def move_playlist_rows(self, session: scoped_session, def move_playlist_rows(self, session: Session,
playlistrows: List[PlaylistRows]) -> None: playlistrows: List[PlaylistRows]) -> None:
""" """
Move passed playlist rows to another playlist Move passed playlist rows to another playlist
@ -1326,7 +1325,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: scoped_session, def this_is_the_next_playlist_row(self, session: Session,
plr: PlaylistRows, plr: PlaylistRows,
playlist_tab: PlaylistTab) -> None: playlist_tab: PlaylistTab) -> None:
""" """
@ -1469,9 +1468,6 @@ 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()
@ -1504,7 +1500,7 @@ class Window(QMainWindow, Ui_MainWindow):
class CartDialog(QDialog): class CartDialog(QDialog):
"""Edit cart details""" """Edit cart details"""
def __init__(self, parent: QMainWindow, session: scoped_session, def __init__(self, parent: QMainWindow, session: Session,
cart: Carts) -> None: cart: Carts) -> None:
""" """
Manage carts Manage carts
@ -1541,7 +1537,7 @@ class CartDialog(QDialog):
class DbDialog(QDialog): class DbDialog(QDialog):
"""Select track from database""" """Select track from database"""
def __init__(self, parent: QMainWindow, session: scoped_session, def __init__(self, parent: QMainWindow, session: 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
@ -1779,9 +1775,7 @@ if __name__ == "__main__":
app = QApplication(sys.argv) app = QApplication(sys.argv)
win = Window() win = Window()
win.show() win.show()
status = app.exec() sys.exit(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

View File

@ -1205,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.
@ -1360,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) -> PlaylistRows: row: int) -> int:
"""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))
@ -1722,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 >= 0: if record and record.f_int is not None:
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)

View File

@ -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

17
app/ui_helpers.py Normal file
View File

@ -0,0 +1,17 @@
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)

View File

@ -70,4 +70,11 @@ 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 Executable file
View File

@ -0,0 +1,94 @@
#!/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}")

View File

@ -45,6 +45,5 @@ mypy_path = "/home/kae/.cache/pypoetry/virtualenvs/musicmuster-oWgGw1IG-py3.9:/h
plugins = "sqlalchemy.ext.mypy.plugin" plugins = "sqlalchemy.ext.mypy.plugin"
[tool.vulture] [tool.vulture]
exclude = ["migrations", "app/ui", "archive"] exclude = ["migrations"]
paths = ["app"] paths = ["app"]
make_whitelist = true

16
test.py
View File

@ -2,28 +2,28 @@
from PyQt5 import QtGui, QtWidgets from PyQt5 import QtGui, QtWidgets
class TabBar(QtWidgets.QTabBar): class TabBar(QtWidgets.QTabWidget):
def paintEvent(self, event): def paintEvent(self, event):
painter = QtWidgets.QStylePainter(self) painter = QtGui.QStylePainter(self)
option = QtWidgets.QStyleOptionTab() option = QtGui.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(QtWidgets.QStyle.CE_TabBarTabShape, option) painter.drawControl(QtGui.QStyle.CE_TabBarTabShape, option)
painter.drawControl(QtWidgets.QStyle.CE_TabBarTabLabel, option) painter.drawControl(QtGui.QStyle.CE_TabBarTabLabel, option)
class Window(QtWidgets.QTabWidget): class Window(QtWidgets.QTabWidget):
def __init__(self): def __init__(self):
QtWidgets.QTabWidget.__init__(self) QtGui.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(QtWidgets.QWidget(self), color) self.addTab(QtGui.QWidget(self), color)
if __name__ == '__main__': if __name__ == '__main__':
import sys import sys
app = QtWidgets.QApplication(sys.argv) app = QtGui.QApplication(sys.argv)
window = Window() window = Window()
window.resize(420, 200) window.resize(420, 200)
window.show() window.show()

View File

@ -5,6 +5,7 @@ from app.models import (
Notes, Notes,
Playdates, Playdates,
Playlists, Playlists,
PlaylistTracks,
Tracks, Tracks,
) )
@ -204,11 +205,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
_ = Notes(session, playlist.id, note1_row, note1_text) note1 = Notes(session, playlist.id, note1_row, note1_text)
note2_text = "note2 text" note2_text = "note2 text"
note2_row = 19 note2_row = 19
_ = Notes(session, playlist.id, note2_row, note2_text) note2 = 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]