#! /usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Copyright 2022 , Nils Hilbricht , Germany ( https : / / www . hilbricht . net )
This file is part of the Laborejo Software Suite ( https : / / www . laborejo . org ) ,
Laborejo2 is free software : you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program . If not , see < http : / / www . gnu . org / licenses / > .
"""
import logging ; logger = logging . getLogger ( __name__ ) ; logger . info ( " import " )
#Third Party
from PyQt5 import QtCore , QtGui , QtWidgets
#Template Modules
#Our Modules
import engine . api as api
class TrackListWidget ( QtWidgets . QWidget ) :
""" Small container wrapper for a title, checkboxes etc. """
def __init__ ( self , mainWindow , parentSplitter ) :
super ( ) . __init__ ( parentSplitter )
self . mainWindow = mainWindow
self . layout = QtWidgets . QVBoxLayout ( self )
self . _currentlySelectedCursorExportDict = None
self . trackLabel = QtWidgets . QLabel ( " Hello " )
self . showEverythingCheckBox = QtWidgets . QCheckBox ( QtCore . QCoreApplication . translate ( " TrackListWidget " , " Show Everything " ) )
self . showEverythingCheckBox . setChecked ( True )
self . showEverythingCheckBox . toggled . connect ( self . reactShowEverything )
self . soloPlaybackCheckbox = QtWidgets . QCheckBox ( QtCore . QCoreApplication . translate ( " TrackListWidget " , " Playback Solo " ) )
self . soloPlaybackCheckbox . toggled . connect ( self . reactToggleSolo )
self . realTrackListWidget = _TrackListWidget ( mainWindow , self )
self . layout . addWidget ( self . trackLabel )
self . layout . addWidget ( self . soloPlaybackCheckbox )
self . layout . addWidget ( self . showEverythingCheckBox )
self . layout . addWidget ( self . realTrackListWidget , stretch = 1 )
api . callbacks . setCursor . append ( self . callback_setCursor )
def reactShowEverything ( self , newState : bool ) :
self . realTrackListWidget . showEverything ( newState )
def callback_setCursor ( self , c : dict ) :
self . _currentlySelectedCursorExportDict = c
self . trackLabel . setText ( " <b> {} - {} </b> " . format ( c [ " trackIndex " ] + 1 , c [ " trackName " ] ) )
self . soloPlaybackCheckbox . blockSignals ( True ) #we don't want to trigger reactToggleSolo. Even if that is not recursive (qt prevents signals when nothing actually changed), it is a double call to the api with the same value
self . soloPlaybackCheckbox . setChecked ( c [ " solo " ] )
self . soloPlaybackCheckbox . blockSignals ( False )
def reactToggleSolo ( self , newState : bool ) :
trId = self . _currentlySelectedCursorExportDict [ " trackId " ]
api . trackSolo ( trId , newState )
class _TrackListWidget ( QtWidgets . QListWidget ) :
""" The TrackListWidget holds all tracks as text-only variants with cursor access.
It will show one track at a time , the current one .
It is meant as alternative access for hard - to - reach items , such as zero - duration - items .
The cursor decides which track is currently handled as items .
But track content changes can come it for any track at any time .
In opposite to the ScoreScene we do not deal with deleted and hidden tracks .
We recreate the items on track change .
"""
def __init__ ( self , mainWindow , parentWidget ) :
super ( ) . __init__ ( )
self . mainWindow = mainWindow
self . _lastCurrentTrackId = None
self . _firstBlockNames = { } # engineTrackId : string
self . tracks = { } # engineTrackId : engineExportData
self . itemPressed . connect ( self . _react_itemPressed )
self . _showEverything = True
api . callbacks . tracksChanged . append ( self . syncTracks )
api . callbacks . updateTrack . append ( self . updateTrack )
api . callbacks . setCursor . append ( self . setCursor )
api . callbacks . updateBlockTrack . append ( self . updateBlockList ) #to get the block names
def showEverything ( self , state : bool ) :
""" Can be called externally to filter out the most common items.
The indexing and cursor will still work """
self . _showEverything = state
self . showTrack ( self . _lastCurrentTrackId )
def setCursor ( self , cursorExportObject ) :
if not self . _lastCurrentTrackId == cursorExportObject [ " trackId " ] :
self . _lastCurrentTrackId = cursorExportObject [ " trackId " ]
self . showTrack ( cursorExportObject [ " trackId " ] )
self . blockSignals ( True )
self . setCurrentRow ( cursorExportObject [ " position " ] + 1 ) #+1 offset for the initial block name
self . blockSignals ( False )
def _react_itemPressed ( self , item ) :
""" This is gui->api cursor setting
It only happens when the mouse etc . actually pressed an item .
This list cannot be activated by cursor keys etc . directly .
"""
i = self . currentRow ( ) #currentRow is calculated after we already are on the item.
if i > 0 :
i - = 1 #-1 because we have the artificial first block item that the engine didn't send us
api . toPosition ( i )
def syncTracks ( self , listOfStaticTrackRepresentations ) :
""" Handles the number of tracks and track meta-data changes,
but not track contents , which is handled by self . updateTrack through a different callback """
for trackExportObject in listOfStaticTrackRepresentations :
if not trackExportObject [ " id " ] in self . tracks :
self . tracks [ trackExportObject [ " id " ] ] = None
def updateBlockList ( self , trackId , listOfBlockExportDicts ) :
""" This comes in for every small item change. But only per track.
Blocknames in our list are done by using the artificial block end markers , but the first block does not have that .
We store this for self . showTrack
"""
self . _firstBlockNames [ trackId ] = listOfBlockExportDicts [ 0 ] [ " name " ]
#updateTrack callback arrives before updateBlockTrack.
#We need both, but we also need to update the first item directly after a name change
if trackId == self . _lastCurrentTrackId :
self . item ( 0 ) . setText ( " == " + self . _firstBlockNames [ trackId ] )
def updateTrack ( self , trackId , staticRepresentationList ) :
""" Callback: The content of a single track has changed """
if not trackId in self . tracks :
#hidden track. But this can still happen through the data editor
return
self . tracks [ trackId ] = staticRepresentationList
if trackId == self . _lastCurrentTrackId :
self . showTrack ( trackId )
def showTrack ( self , trackId ) :
""" Cursor moved to a different track.
This is essentially destroy - and - recreate in Qt """
self . clear ( )
staticRepresentationList = self . tracks [ trackId ]
count = 0
#Block entries are done with BlockEndMarker, but there is no for the first.
self . addItem ( " == " + self . _firstBlockNames [ self . _lastCurrentTrackId ] )
filterTypes = set ( ( " Chord " , " Rest " ) )
if self . _showEverything :
for staticItem in staticRepresentationList :
self . addItem ( staticItem [ " UIstring " ] )
else : #Hide Chords and Rests. The indexing and cursor will still work
for staticItem in staticRepresentationList :
item = QtWidgets . QListWidgetItem ( staticItem [ " UIstring " ] )
self . addItem ( item )
if staticItem [ " type " ] in filterTypes :
item . setHidden ( True )
self . addItem ( " | " ) #Appending