Add function type hints. Section headers and note colours working
This commit is contained in:
parent
4f03306aff
commit
7a14651bd7
@ -11,14 +11,12 @@ class Config(object):
|
||||
COLOUR_CURRENT_PLAYLIST = "#7eca8f"
|
||||
COLOUR_CURRENT_TAB = "#248f24"
|
||||
COLOUR_ENDING_TIMER = "#dc3545"
|
||||
COLOUR_EVEN_PLAYLIST = "#d9d9d9"
|
||||
COLOUR_LONG_START = "#dc3545"
|
||||
COLOUR_NEXT_HEADER = "#fff3cd"
|
||||
COLOUR_NEXT_PLAYLIST = "#ffc107"
|
||||
COLOUR_NEXT_TAB = "#b38600"
|
||||
COLOUR_NORMAL_TAB = "#000000"
|
||||
COLOUR_NOTES_PLAYLIST = "#b8daff"
|
||||
COLOUR_ODD_PLAYLIST = "#f2f2f2"
|
||||
COLOUR_PREVIOUS_HEADER = "#f8d7da"
|
||||
COLOUR_UNREADABLE = "#dc3545"
|
||||
COLOUR_WARNING_TIMER = "#ffc107"
|
||||
|
||||
@ -2,9 +2,10 @@ import inspect
|
||||
import logging
|
||||
import os
|
||||
from config import Config
|
||||
from sqlalchemy import create_engine
|
||||
from contextlib import contextmanager
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import (sessionmaker, scoped_session)
|
||||
from typing import Generator
|
||||
|
||||
from log import log
|
||||
|
||||
@ -38,7 +39,7 @@ engine = create_engine(
|
||||
|
||||
|
||||
@contextmanager
|
||||
def Session():
|
||||
def Session() -> Generator[scoped_session, None, None]:
|
||||
frame = inspect.stack()[2]
|
||||
file = frame.filename
|
||||
function = frame.function
|
||||
|
||||
@ -11,7 +11,7 @@ from config import Config
|
||||
class LevelTagFilter(logging.Filter):
|
||||
"""Add leveltag"""
|
||||
|
||||
def filter(self, record):
|
||||
def filter(self, record: logging.LogRecord):
|
||||
# Extract the first character of the level name
|
||||
record.leveltag = record.levelname[0]
|
||||
|
||||
@ -23,7 +23,7 @@ class LevelTagFilter(logging.Filter):
|
||||
class DebugStdoutFilter(logging.Filter):
|
||||
"""Filter debug messages sent to stdout"""
|
||||
|
||||
def filter(self, record):
|
||||
def filter(self, record: logging.LogRecord):
|
||||
if record.levelno != logging.DEBUG:
|
||||
return True
|
||||
if record.module in Config.DEBUG_MODULES:
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/python3
|
||||
#
|
||||
# import os.path
|
||||
# import re
|
||||
import re
|
||||
#
|
||||
from dbconfig import Session
|
||||
#
|
||||
@ -46,8 +46,8 @@ from sqlalchemy.orm.exc import (
|
||||
# from log import log.debug, log.error
|
||||
#
|
||||
Base = declarative_base()
|
||||
#
|
||||
#
|
||||
|
||||
|
||||
# Database classes
|
||||
class NoteColours(Base):
|
||||
__tablename__ = 'notecolours'
|
||||
@ -93,38 +93,40 @@ class NoteColours(Base):
|
||||
#
|
||||
# return session.query(NoteColours).filter(
|
||||
# NoteColours.id == note_id).first()
|
||||
#
|
||||
# @staticmethod
|
||||
# def get_colour(session: Session, text: str) -> Optional[str]:
|
||||
# """
|
||||
# Parse text and return colour string if matched, else None
|
||||
# """
|
||||
#
|
||||
# for rec in (
|
||||
# session.query(NoteColours)
|
||||
# .filter(NoteColours.enabled.is_(True))
|
||||
# .order_by(NoteColours.order)
|
||||
# .all()
|
||||
# ):
|
||||
# if rec.is_regex:
|
||||
# flags = re.UNICODE
|
||||
# if not rec.is_casesensitive:
|
||||
# flags |= re.IGNORECASE
|
||||
# p = re.compile(rec.substring, flags)
|
||||
# if p.match(text):
|
||||
# return rec.colour
|
||||
# else:
|
||||
# if rec.is_casesensitive:
|
||||
# if rec.substring in text:
|
||||
# return rec.colour
|
||||
# else:
|
||||
# if rec.substring.lower() in text.lower():
|
||||
# return rec.colour
|
||||
#
|
||||
# return None
|
||||
#
|
||||
#
|
||||
#class Notes(Base):
|
||||
|
||||
@staticmethod
|
||||
def get_colour(session: Session, text: str) -> Optional[str]:
|
||||
"""
|
||||
Parse text and return colour string if matched, else None
|
||||
"""
|
||||
|
||||
if not text:
|
||||
return None
|
||||
|
||||
for rec in session.execute(
|
||||
select(NoteColours)
|
||||
.filter(NoteColours.enabled.is_(True))
|
||||
.order_by(NoteColours.order)
|
||||
).scalars().all():
|
||||
if rec.is_regex:
|
||||
flags = re.UNICODE
|
||||
if not rec.is_casesensitive:
|
||||
flags |= re.IGNORECASE
|
||||
p = re.compile(rec.substring, flags)
|
||||
if p.match(text):
|
||||
return rec.colour
|
||||
else:
|
||||
if rec.is_casesensitive:
|
||||
if rec.substring in text:
|
||||
return rec.colour
|
||||
else:
|
||||
if rec.substring.lower() in text.lower():
|
||||
return rec.colour
|
||||
|
||||
return None
|
||||
|
||||
|
||||
# class Notes(Base):
|
||||
# __tablename__ = 'notes'
|
||||
#
|
||||
# id: int = Column(Integer, primary_key=True, autoincrement=True)
|
||||
@ -514,7 +516,7 @@ class Settings(Base):
|
||||
|
||||
return int_setting
|
||||
|
||||
def update(self, session: Session, data):
|
||||
def update(self, session: Session, data: "Settings"):
|
||||
for key, value in data.items():
|
||||
assert hasattr(self, key)
|
||||
setattr(self, key, value)
|
||||
|
||||
@ -17,14 +17,14 @@ import sys
|
||||
# from PyQt5.QtWebEngineWidgets import QWebEngineView as QWebView
|
||||
from PyQt5.QtWidgets import (
|
||||
QApplication,
|
||||
# QDialog,
|
||||
# QFileDialog,
|
||||
# QInputDialog,
|
||||
# QLabel,
|
||||
# QLineEdit,
|
||||
# QListWidgetItem,
|
||||
# QDialog,
|
||||
# QFileDialog,
|
||||
# QInputDialog,
|
||||
# QLabel,
|
||||
# QLineEdit,
|
||||
# QListWidgetItem,
|
||||
QMainWindow,
|
||||
# QMessageBox,
|
||||
# QMessageBox,
|
||||
)
|
||||
#
|
||||
from dbconfig import engine, Session
|
||||
@ -494,7 +494,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
# # also be saved to database
|
||||
# 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"""
|
||||
|
||||
with Session() as session:
|
||||
|
||||
146
app/playlists.py
146
app/playlists.py
@ -6,9 +6,9 @@ from typing import List, Optional
|
||||
#
|
||||
# from PyQt5 import QtCore
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.Qt import QFont
|
||||
from PyQt5.QtGui import (
|
||||
QColor,
|
||||
QFont,
|
||||
# QDropEvent
|
||||
)
|
||||
# from PyQt5 import QtWidgets
|
||||
@ -27,12 +27,12 @@ from PyQt5.QtWidgets import (
|
||||
#
|
||||
import helpers
|
||||
# import os
|
||||
# import re
|
||||
import re
|
||||
# import subprocess
|
||||
# import threading
|
||||
#
|
||||
from config import Config
|
||||
from datetime import datetime #, timedelta
|
||||
from datetime import datetime # , timedelta
|
||||
from helpers import (
|
||||
get_relative_date,
|
||||
# open_in_audacity
|
||||
@ -45,10 +45,10 @@ from models import (
|
||||
PlaylistRows,
|
||||
Settings,
|
||||
Tracks,
|
||||
# NoteColours
|
||||
NoteColours
|
||||
)
|
||||
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:
|
||||
@ -101,7 +101,7 @@ class PlaylistTab(QTableWidget):
|
||||
PLAYLISTROW_ID = Qt.UserRole + 2
|
||||
|
||||
def __init__(self, musicmuster: QMainWindow, session: Session,
|
||||
playlist_id: int, *args, **kwargs):
|
||||
playlist_id: int, *args, **kwargs) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
self.musicmuster: QMainWindow = musicmuster
|
||||
self.playlist_id: int = playlist_id
|
||||
@ -166,7 +166,7 @@ class PlaylistTab(QTableWidget):
|
||||
# Now load our tracks and notes
|
||||
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.
|
||||
|
||||
@ -380,14 +380,10 @@ class PlaylistTab(QTableWidget):
|
||||
self.insertRow(row)
|
||||
|
||||
# Add row metadata to userdata column
|
||||
item: QTableWidgetItem = QTableWidgetItem()
|
||||
item.setData(self.ROW_METADATA, 0)
|
||||
self.setItem(row, columns['userdata'].idx, item)
|
||||
|
||||
# Prepare notes, start and end items for later
|
||||
notes_item = QTableWidgetItem(row_data.note)
|
||||
start_item = QTableWidgetItem()
|
||||
end_item = QTableWidgetItem()
|
||||
userdata_item = QTableWidgetItem()
|
||||
userdata_item.setData(self.ROW_METADATA, 0)
|
||||
userdata_item.setData(self.PLAYLISTROW_ID, row_data.id)
|
||||
self.setItem(row, columns['userdata'].idx, userdata_item)
|
||||
|
||||
if row_data.track_id:
|
||||
# Add track details to items
|
||||
@ -395,13 +391,28 @@ class PlaylistTab(QTableWidget):
|
||||
start_gap_item = QTableWidgetItem(str(start_gap))
|
||||
if start_gap and start_gap >= 500:
|
||||
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)
|
||||
self.setItem(row, columns['title'].idx, title_item)
|
||||
|
||||
artist_item = QTableWidgetItem(row_data.track.artist)
|
||||
self.setItem(row, columns['artist'].idx, artist_item)
|
||||
|
||||
duration_item = QTableWidgetItem(
|
||||
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_played_str = get_relative_date(last_playtime)
|
||||
@ -412,39 +423,21 @@ class PlaylistTab(QTableWidget):
|
||||
if not helpers.file_is_readable(row_data.track.path):
|
||||
self._set_unreadable_row(row)
|
||||
|
||||
else:
|
||||
# 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:
|
||||
# Save track_id
|
||||
userdata_item.setData(self.ROW_TRACK_ID, row_data.track_id)
|
||||
|
||||
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.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:
|
||||
self.save_playlist(session)
|
||||
@ -870,6 +863,15 @@ class PlaylistTab(QTableWidget):
|
||||
# Cycle through all rows
|
||||
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
|
||||
track_id = self._get_row_track_id(row)
|
||||
track = None
|
||||
@ -903,6 +905,11 @@ class PlaylistTab(QTableWidget):
|
||||
else:
|
||||
self.showRow(row)
|
||||
|
||||
# Colour any note
|
||||
if note_text:
|
||||
(self.item(row, columns['row_notes'].idx)
|
||||
.setBackground(QColor(note_colour)))
|
||||
|
||||
# Render playing track
|
||||
if row == current_row:
|
||||
# Set start time
|
||||
@ -973,21 +980,10 @@ class PlaylistTab(QTableWidget):
|
||||
self._set_row_end_time(row, None)
|
||||
# Don't dim unplayed tracks
|
||||
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
|
||||
|
||||
# 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 not in note_text.lower():
|
||||
self.hideRow(row)
|
||||
@ -1010,10 +1006,6 @@ class PlaylistTab(QTableWidget):
|
||||
elif note_text.endswith("+"):
|
||||
section_start_row = row
|
||||
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(
|
||||
row, QColor(note_colour)
|
||||
)
|
||||
@ -1249,7 +1241,7 @@ class PlaylistTab(QTableWidget):
|
||||
# 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"""
|
||||
|
||||
playlistrow_id = (self.item(row, columns['userdata'].idx)
|
||||
@ -1257,7 +1249,7 @@ class PlaylistTab(QTableWidget):
|
||||
|
||||
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"""
|
||||
|
||||
track_id = (self.item(row, columns['userdata'].idx)
|
||||
@ -1309,20 +1301,20 @@ class PlaylistTab(QTableWidget):
|
||||
return row[0]
|
||||
else:
|
||||
return None
|
||||
#
|
||||
# @staticmethod
|
||||
# def _get_note_text_time(text: str) -> Optional[datetime]:
|
||||
# """Return time specified as @hh:mm:ss in text"""
|
||||
#
|
||||
# match = start_time_re.search(text)
|
||||
# if not match:
|
||||
# return None
|
||||
#
|
||||
# try:
|
||||
# return datetime.strptime(match.group(0)[1:],
|
||||
# Config.NOTE_TIME_FORMAT)
|
||||
# except ValueError:
|
||||
# return None
|
||||
|
||||
@staticmethod
|
||||
def _get_note_text_time(text: str) -> Optional[datetime]:
|
||||
"""Return time specified as @hh:mm:ss in text"""
|
||||
|
||||
match = start_time_re.search(text)
|
||||
if not match:
|
||||
return None
|
||||
|
||||
try:
|
||||
return datetime.strptime(match.group(0)[1:],
|
||||
Config.NOTE_TIME_FORMAT)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
def _get_played_track_rows(self) -> List[int]:
|
||||
"""Return rows marked as played, or None"""
|
||||
@ -1751,7 +1743,7 @@ class PlaylistTab(QTableWidget):
|
||||
|
||||
j: int
|
||||
|
||||
for j in range(2, self.columnCount()):
|
||||
for j in range(1, self.columnCount()):
|
||||
if self.item(row, j):
|
||||
self.item(row, j).setBackground(colour)
|
||||
#
|
||||
|
||||
Loading…
Reference in New Issue
Block a user