Add function type hints. Section headers and note colours working

This commit is contained in:
Keith Edmunds 2022-08-05 21:52:17 +01:00
parent 4f03306aff
commit 7a14651bd7
6 changed files with 120 additions and 127 deletions

View File

@ -11,14 +11,12 @@ class Config(object):
COLOUR_CURRENT_PLAYLIST = "#7eca8f" COLOUR_CURRENT_PLAYLIST = "#7eca8f"
COLOUR_CURRENT_TAB = "#248f24" COLOUR_CURRENT_TAB = "#248f24"
COLOUR_ENDING_TIMER = "#dc3545" COLOUR_ENDING_TIMER = "#dc3545"
COLOUR_EVEN_PLAYLIST = "#d9d9d9"
COLOUR_LONG_START = "#dc3545" COLOUR_LONG_START = "#dc3545"
COLOUR_NEXT_HEADER = "#fff3cd" COLOUR_NEXT_HEADER = "#fff3cd"
COLOUR_NEXT_PLAYLIST = "#ffc107" COLOUR_NEXT_PLAYLIST = "#ffc107"
COLOUR_NEXT_TAB = "#b38600" COLOUR_NEXT_TAB = "#b38600"
COLOUR_NORMAL_TAB = "#000000" COLOUR_NORMAL_TAB = "#000000"
COLOUR_NOTES_PLAYLIST = "#b8daff" COLOUR_NOTES_PLAYLIST = "#b8daff"
COLOUR_ODD_PLAYLIST = "#f2f2f2"
COLOUR_PREVIOUS_HEADER = "#f8d7da" COLOUR_PREVIOUS_HEADER = "#f8d7da"
COLOUR_UNREADABLE = "#dc3545" COLOUR_UNREADABLE = "#dc3545"
COLOUR_WARNING_TIMER = "#ffc107" COLOUR_WARNING_TIMER = "#ffc107"

View File

@ -2,9 +2,10 @@ import inspect
import logging import logging
import os import os
from config import Config from config import Config
from sqlalchemy import create_engine
from contextlib import contextmanager from contextlib import contextmanager
from sqlalchemy import create_engine
from sqlalchemy.orm import (sessionmaker, scoped_session) from sqlalchemy.orm import (sessionmaker, scoped_session)
from typing import Generator
from log import log from log import log
@ -38,7 +39,7 @@ engine = create_engine(
@contextmanager @contextmanager
def Session(): def Session() -> Generator[scoped_session, None, None]:
frame = inspect.stack()[2] frame = inspect.stack()[2]
file = frame.filename file = frame.filename
function = frame.function function = frame.function

View File

@ -11,7 +11,7 @@ from config import Config
class LevelTagFilter(logging.Filter): class LevelTagFilter(logging.Filter):
"""Add leveltag""" """Add leveltag"""
def filter(self, record): def filter(self, record: logging.LogRecord):
# Extract the first character of the level name # Extract the first character of the level name
record.leveltag = record.levelname[0] record.leveltag = record.levelname[0]
@ -23,7 +23,7 @@ class LevelTagFilter(logging.Filter):
class DebugStdoutFilter(logging.Filter): class DebugStdoutFilter(logging.Filter):
"""Filter debug messages sent to stdout""" """Filter debug messages sent to stdout"""
def filter(self, record): def filter(self, record: logging.LogRecord):
if record.levelno != logging.DEBUG: if record.levelno != logging.DEBUG:
return True return True
if record.module in Config.DEBUG_MODULES: if record.module in Config.DEBUG_MODULES:

View File

@ -1,7 +1,7 @@
#!/usr/bin/python3 #!/usr/bin/python3
# #
# import os.path # import os.path
# import re import re
# #
from dbconfig import Session from dbconfig import Session
# #
@ -46,8 +46,8 @@ from sqlalchemy.orm.exc import (
# from log import log.debug, log.error # from log import log.debug, log.error
# #
Base = declarative_base() Base = declarative_base()
#
#
# Database classes # Database classes
class NoteColours(Base): class NoteColours(Base):
__tablename__ = 'notecolours' __tablename__ = 'notecolours'
@ -93,38 +93,40 @@ class NoteColours(Base):
# #
# return session.query(NoteColours).filter( # return session.query(NoteColours).filter(
# NoteColours.id == note_id).first() # NoteColours.id == note_id).first()
#
# @staticmethod @staticmethod
# def get_colour(session: Session, text: str) -> Optional[str]: def get_colour(session: Session, text: str) -> Optional[str]:
# """ """
# Parse text and return colour string if matched, else None Parse text and return colour string if matched, else None
# """ """
#
# for rec in ( if not text:
# session.query(NoteColours) return None
# .filter(NoteColours.enabled.is_(True))
# .order_by(NoteColours.order) for rec in session.execute(
# .all() select(NoteColours)
# ): .filter(NoteColours.enabled.is_(True))
# if rec.is_regex: .order_by(NoteColours.order)
# flags = re.UNICODE ).scalars().all():
# if not rec.is_casesensitive: if rec.is_regex:
# flags |= re.IGNORECASE flags = re.UNICODE
# p = re.compile(rec.substring, flags) if not rec.is_casesensitive:
# if p.match(text): flags |= re.IGNORECASE
# return rec.colour p = re.compile(rec.substring, flags)
# else: if p.match(text):
# if rec.is_casesensitive: return rec.colour
# if rec.substring in text: else:
# return rec.colour if rec.is_casesensitive:
# else: if rec.substring in text:
# if rec.substring.lower() in text.lower(): return rec.colour
# return rec.colour else:
# if rec.substring.lower() in text.lower():
# return None return rec.colour
#
# return None
#class Notes(Base):
# class Notes(Base):
# __tablename__ = 'notes' # __tablename__ = 'notes'
# #
# id: int = Column(Integer, primary_key=True, autoincrement=True) # id: int = Column(Integer, primary_key=True, autoincrement=True)
@ -514,7 +516,7 @@ class Settings(Base):
return int_setting return int_setting
def update(self, session: Session, data): def update(self, session: 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)

View File

@ -17,14 +17,14 @@ import sys
# from PyQt5.QtWebEngineWidgets import QWebEngineView as QWebView # from PyQt5.QtWebEngineWidgets import QWebEngineView as QWebView
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QApplication, QApplication,
# QDialog, # QDialog,
# QFileDialog, # QFileDialog,
# QInputDialog, # QInputDialog,
# QLabel, # QLabel,
# QLineEdit, # QLineEdit,
# QListWidgetItem, # QListWidgetItem,
QMainWindow, QMainWindow,
# QMessageBox, # QMessageBox,
) )
# #
from dbconfig import engine, Session from dbconfig import engine, Session
@ -494,7 +494,7 @@ class Window(QMainWindow, Ui_MainWindow):
# # also be saved to database # # also be saved to database
# self.visible_playlist_tab().insert_track(session, track) # self.visible_playlist_tab().insert_track(session, track)
def _load_last_playlists(self): def _load_last_playlists(self) -> None:
"""Load the playlists that were open when the last session closed""" """Load the playlists that were open when the last session closed"""
with Session() as session: with Session() as session:

View File

@ -6,9 +6,9 @@ from typing import List, Optional
# #
# from PyQt5 import QtCore # from PyQt5 import QtCore
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from PyQt5.Qt import QFont
from PyQt5.QtGui import ( from PyQt5.QtGui import (
QColor, QColor,
QFont,
# QDropEvent # QDropEvent
) )
# from PyQt5 import QtWidgets # from PyQt5 import QtWidgets
@ -27,12 +27,12 @@ from PyQt5.QtWidgets import (
# #
import helpers import helpers
# import os # import os
# import re import re
# import subprocess # import subprocess
# import threading # import threading
# #
from config import Config from config import Config
from datetime import datetime #, timedelta from datetime import datetime # , timedelta
from helpers import ( from helpers import (
get_relative_date, get_relative_date,
# open_in_audacity # open_in_audacity
@ -45,10 +45,10 @@ from models import (
PlaylistRows, PlaylistRows,
Settings, Settings,
Tracks, Tracks,
# NoteColours NoteColours
) )
from dbconfig import Session from dbconfig import Session
# start_time_re = re.compile(r"@\d\d:\d\d:\d\d") start_time_re = re.compile(r"@\d\d:\d\d:\d\d")
class RowMeta: class RowMeta:
@ -101,7 +101,7 @@ class PlaylistTab(QTableWidget):
PLAYLISTROW_ID = Qt.UserRole + 2 PLAYLISTROW_ID = Qt.UserRole + 2
def __init__(self, musicmuster: QMainWindow, session: Session, def __init__(self, musicmuster: QMainWindow, session: Session,
playlist_id: int, *args, **kwargs): playlist_id: int, *args, **kwargs) -> None:
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.musicmuster: QMainWindow = musicmuster self.musicmuster: QMainWindow = musicmuster
self.playlist_id: int = playlist_id self.playlist_id: int = playlist_id
@ -166,7 +166,7 @@ class PlaylistTab(QTableWidget):
# Now load our tracks and notes # Now load our tracks and notes
self.populate(session, self.playlist_id) self.populate(session, self.playlist_id)
def _column_resize(self, idx, old, new): def _column_resize(self, idx: int, old: int, new: int) -> None:
""" """
Called when column widths are changed. Called when column widths are changed.
@ -380,14 +380,10 @@ class PlaylistTab(QTableWidget):
self.insertRow(row) self.insertRow(row)
# Add row metadata to userdata column # Add row metadata to userdata column
item: QTableWidgetItem = QTableWidgetItem() userdata_item = QTableWidgetItem()
item.setData(self.ROW_METADATA, 0) userdata_item.setData(self.ROW_METADATA, 0)
self.setItem(row, columns['userdata'].idx, item) userdata_item.setData(self.PLAYLISTROW_ID, row_data.id)
self.setItem(row, columns['userdata'].idx, userdata_item)
# Prepare notes, start and end items for later
notes_item = QTableWidgetItem(row_data.note)
start_item = QTableWidgetItem()
end_item = QTableWidgetItem()
if row_data.track_id: if row_data.track_id:
# Add track details to items # Add track details to items
@ -395,13 +391,28 @@ class PlaylistTab(QTableWidget):
start_gap_item = QTableWidgetItem(str(start_gap)) start_gap_item = QTableWidgetItem(str(start_gap))
if start_gap and start_gap >= 500: if start_gap and start_gap >= 500:
start_gap_item.setBackground(QColor(Config.COLOUR_LONG_START)) start_gap_item.setBackground(QColor(Config.COLOUR_LONG_START))
self.setItem(row, columns['start_gap'].idx, start_gap_item)
title_item = QTableWidgetItem(row_data.track.title) title_item = QTableWidgetItem(row_data.track.title)
self.setItem(row, columns['title'].idx, title_item)
artist_item = QTableWidgetItem(row_data.track.artist) artist_item = QTableWidgetItem(row_data.track.artist)
self.setItem(row, columns['artist'].idx, artist_item)
duration_item = QTableWidgetItem( duration_item = QTableWidgetItem(
helpers.ms_to_mmss(row_data.track.duration)) helpers.ms_to_mmss(row_data.track.duration))
self.setItem(row, columns['duration'].idx, duration_item)
start_item = QTableWidgetItem()
self.setItem(row, columns['start_time'].idx, start_item)
end_item = QTableWidgetItem()
self.setItem(row, columns['end_time'].idx, end_item)
# As we have track info, any notes should be contained in
# the notes column
notes_item = QTableWidgetItem(row_data.note)
self.setItem(row, columns['row_notes'].idx, notes_item)
last_playtime = Playdates.last_played(session, row_data.track.id) last_playtime = Playdates.last_played(session, row_data.track.id)
last_played_str = get_relative_date(last_playtime) last_played_str = get_relative_date(last_playtime)
@ -412,39 +423,21 @@ class PlaylistTab(QTableWidget):
if not helpers.file_is_readable(row_data.track.path): if not helpers.file_is_readable(row_data.track.path):
self._set_unreadable_row(row) self._set_unreadable_row(row)
else: # Save track_id
# This is a note row so make empty items (row background
# won't be coloured without items present)
start_gap_item = QTableWidgetItem()
title_item = QTableWidgetItem()
artist_item = QTableWidgetItem()
duration_item = QTableWidgetItem()
last_played_item = QTableWidgetItem()
# Add items to table
self.setItem(row, columns['start_gap'].idx, start_gap_item)
self.setItem(row, columns['title'].idx, title_item)
self.setItem(row, columns['artist'].idx, artist_item)
self.setItem(row, columns['duration'].idx, duration_item)
self.setItem(row, columns['start_time'].idx, start_item)
self.setItem(row, columns['end_time'].idx, end_item)
self.setItem(row, columns['row_notes'].idx, notes_item)
# Save track_id and playlistrow_id
userdata_item = QTableWidgetItem()
if row_data.track_id:
userdata_item.setData(self.ROW_TRACK_ID, row_data.track_id) userdata_item.setData(self.ROW_TRACK_ID, row_data.track_id)
else: else:
# This is a section header so make empty items (row
# background won't be coloured without items present). Any
# notes should displayed starting in column 0
for i in range(2, len(columns) - 1):
self.setItem(row, i, QTableWidgetItem())
notes_item = QTableWidgetItem(row_data.note)
self.setItem(row, 1, notes_item)
self.setSpan(row, 1, 1, len(columns))
# Save (no) track_id
userdata_item.setData(self.ROW_TRACK_ID, 0) userdata_item.setData(self.ROW_TRACK_ID, 0)
userdata_item.setData(self.PLAYLISTROW_ID, row_data.id)
self.setItem(row, columns['userdata'].idx, userdata_item)
# Span note across table
if not row_data.track_id:
self.setSpan(row, columns['row_notes'].idx, len(columns), 1)
# Scroll to new row
self.scrollToItem(title_item, QAbstractItemView.PositionAtCenter)
if repaint: if repaint:
self.save_playlist(session) self.save_playlist(session)
@ -870,6 +863,15 @@ class PlaylistTab(QTableWidget):
# Cycle through all rows # Cycle through all rows
for row in range(self.rowCount()): for row in range(self.rowCount()):
# Extract note text from database to ignore section timings
playlist_row = session.get(PlaylistRows,
self._get_playlistrow_id(row))
note_text = playlist_row.note
# Get note colour
note_colour = NoteColours.get_colour(session, note_text)
if not note_colour:
note_colour = Config.COLOUR_NOTES_PLAYLIST
# Get track if there is one # Get track if there is one
track_id = self._get_row_track_id(row) track_id = self._get_row_track_id(row)
track = None track = None
@ -903,6 +905,11 @@ class PlaylistTab(QTableWidget):
else: else:
self.showRow(row) self.showRow(row)
# Colour any note
if note_text:
(self.item(row, columns['row_notes'].idx)
.setBackground(QColor(note_colour)))
# Render playing track # Render playing track
if row == current_row: if row == current_row:
# Set start time # Set start time
@ -973,21 +980,10 @@ class PlaylistTab(QTableWidget):
self._set_row_end_time(row, None) self._set_row_end_time(row, None)
# Don't dim unplayed tracks # Don't dim unplayed tracks
self._set_row_bold(row) self._set_row_bold(row)
# Stripe rows
if row % 2:
self._set_row_colour(
row, QColor(Config.COLOUR_ODD_PLAYLIST))
else:
self._set_row_colour(
row, QColor(Config.COLOUR_EVEN_PLAYLIST))
continue continue
# No track associated, so this row is a section header # No track associated, so this row is a section header
# Extract note text from database to ignore section timings
playlist_row = session.get(PlaylistTracks,
self._get_playlistrow_id(row))
note_text = playlist_row.note
if filter_text: if filter_text:
if filter_text not in note_text.lower(): if filter_text not in note_text.lower():
self.hideRow(row) self.hideRow(row)
@ -1010,10 +1006,6 @@ class PlaylistTab(QTableWidget):
elif note_text.endswith("+"): elif note_text.endswith("+"):
section_start_row = row section_start_row = row
section_time = 0 section_time = 0
# Set colour
note_colour = NoteColours.get_colour(session, note_text)
if not note_colour:
note_colour = Config.COLOUR_NOTES_PLAYLIST
self._set_row_colour( self._set_row_colour(
row, QColor(note_colour) row, QColor(note_colour)
) )
@ -1249,7 +1241,7 @@ class PlaylistTab(QTableWidget):
# return self._meta_search(RowMeta.NOTE, one=False) # return self._meta_search(RowMeta.NOTE, one=False)
# #
def _get_playlistrow_id(self, row): def _get_playlistrow_id(self, row: int) -> int:
"""Return the playlistrow_id associated with this row""" """Return the playlistrow_id associated with this row"""
playlistrow_id = (self.item(row, columns['userdata'].idx) playlistrow_id = (self.item(row, columns['userdata'].idx)
@ -1257,7 +1249,7 @@ class PlaylistTab(QTableWidget):
return playlistrow_id return playlistrow_id
def _get_row_track_id(self, row): def _get_row_track_id(self, row: int) -> int:
"""Return the track_id associated with this row or None""" """Return the track_id associated with this row or None"""
track_id = (self.item(row, columns['userdata'].idx) track_id = (self.item(row, columns['userdata'].idx)
@ -1309,20 +1301,20 @@ class PlaylistTab(QTableWidget):
return row[0] return row[0]
else: else:
return None return None
#
# @staticmethod @staticmethod
# def _get_note_text_time(text: str) -> Optional[datetime]: def _get_note_text_time(text: str) -> Optional[datetime]:
# """Return time specified as @hh:mm:ss in text""" """Return time specified as @hh:mm:ss in text"""
#
# match = start_time_re.search(text) match = start_time_re.search(text)
# if not match: if not match:
# return None return None
#
# try: try:
# return datetime.strptime(match.group(0)[1:], return datetime.strptime(match.group(0)[1:],
# Config.NOTE_TIME_FORMAT) Config.NOTE_TIME_FORMAT)
# except ValueError: except ValueError:
# return None return None
def _get_played_track_rows(self) -> List[int]: def _get_played_track_rows(self) -> List[int]:
"""Return rows marked as played, or None""" """Return rows marked as played, or None"""
@ -1751,7 +1743,7 @@ class PlaylistTab(QTableWidget):
j: int j: int
for j in range(2, self.columnCount()): for j in range(1, self.columnCount()):
if self.item(row, j): if self.item(row, j):
self.item(row, j).setBackground(colour) self.item(row, j).setBackground(colour)
# #