#! /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 " )
#Standard Library
from contextlib import contextmanager
#Third party
from PyQt5 import QtCore , QtGui , QtWidgets
translate = QtCore . QCoreApplication . translate
#Template
from template . helper import pairwise
#Our own files
from . constantsAndConfigs import constantsAndConfigs
from . designer . trackWidget import Ui_trackGroupWidget
from . submenus import TickWidget , CombinedTickWidget , SecondaryMetricalInstructionMenu
from . customkeysignature import CustomKeySignatureWidget
from . custommetricalinstruction import CustomMetricalInstructionWidget
import engine . api as api
LIST_OF_CLEF_KEYWORDS = api . getPossibleClefKeywords ( ) #TODO: translate? But keep the keyword as index. setData
class TrackWidget ( QtWidgets . QGroupBox ) :
#TODO: ideas: number of blocks, list of block names which CCs are set, review/change durationSettingsSignature, dynamicSettingsSignature
def __init__ ( self , parentDataEditor , trackExportObject ) :
super ( ) . __init__ ( )
self . parentDataEditor = parentDataEditor
self . trackExportObject = trackExportObject
self . ui = Ui_trackGroupWidget ( )
self . ui . setupUi ( self )
self . setAttribute ( QtCore . Qt . WA_DeleteOnClose )
self . trackExportObject = trackExportObject #updated on every self.updateData
self . ui . visibleCheckbox . clicked . connect ( self . visibleToggled ) #only user changes, not through setChecked()
self . ui . audibleCheckbox . clicked . connect ( self . audibleToggled ) #only user changes, not through setChecked()
self . ui . doubleTrackCheckbox . clicked . connect ( self . doubleTrackToggled ) #only user changes, not through setChecked()
#self.ui.upbeatSpinBox.editingFinished.connect(self.upbeatChanged) #only user changes, not through setText() etc.
self . ui . upbeatSpinBox . valueChanged . connect ( self . upbeatChanged ) #also through the tickWidget
self . ui . callTickWidget . clicked . connect ( self . callClickWidgetForUpbeat )
self . ui . nameLineEdit . editingFinished . connect ( self . nameChanged ) #only user changes, not through setText() etc.
self . ui . deleteButton . clicked . connect ( lambda : api . deleteTrack ( self . trackExportObject [ " id " ] ) )
self . ui . midiChannelSpinBox . valueChanged . connect ( self . dataChanged )
self . ui . midiProgramSpinBox . valueChanged . connect ( self . dataChanged )
self . ui . midiBankMsbSpinBox . valueChanged . connect ( self . dataChanged )
self . ui . midiBankLsbSpinBox . valueChanged . connect ( self . dataChanged )
self . ui . midiTransposeSpinBox . valueChanged . connect ( self . dataChanged )
self . ui . instrumentName . editingFinished . connect ( self . nameChanged )
self . ui . shortInstrumentName . editingFinished . connect ( self . nameChanged )
self . ui . initialClef_comboBox . addItems ( LIST_OF_CLEF_KEYWORDS ) #Current index is set in self.updateData
self . ui . initialClef_comboBox . currentIndexChanged . connect ( self . dataChanged )
#Create a menu with checkboxes to allow switching on and off of additional channels for the CC sub-track
#However, we will not use normal checkable Menu actions since they close the menu after triggering. even blockSignals does not prevent closing
self . ccChannels = { }
ccChannelMenu = QtWidgets . QMenu ( )
for i in range ( 1 , 17 ) : #excluding 17
checkbox = QtWidgets . QCheckBox ( ccChannelMenu )
self . ccChannels [ i ] = checkbox
checkbox . stateChanged . connect ( self . dataChanged )
widget = QtWidgets . QWidget ( )
layout = QtWidgets . QFormLayout ( )
layout . setContentsMargins ( 0 , 0 , 0 , 0 ) #left, top, right, bottom in pixel
widget . setLayout ( layout )
widgetAction = QtWidgets . QWidgetAction ( ccChannelMenu )
widgetAction . setDefaultWidget ( widget )
layout . addRow ( str ( i ) . zfill ( 2 ) , checkbox )
ccChannelMenu . addAction ( widgetAction )
#action = QtWidgets.QAction(str(i), ccChannelMenu)
#action.setCheckable(True)
#ccChannelMenu.addAction(action)
self . ui . ccChannelsPushButton . setMenu ( ccChannelMenu )
#TODO: The callbacks below trigger quite often. If you move the mouse wheel in a spin box each increase/decrease sends ALL track settings to the backend which triggers a compelte redraw of the track. This should be measure on performance and improved. But even incremental updates should have an immediate effect on a running playback so "send changes when closing the track editor" is only a compromise". Incremental updates are not a a solution either because even those need a playback update. valueChanged does not have an editingFinished signal like text fields.
#Set up the advanced properties. They should be hidden at the start
#Tab 1: Durations
self . ui . defaultOn . editingFinished . connect ( self . dataChanged ) #only user changes, not through setText() etc.
self . ui . defaultOff . editingFinished . connect ( self . dataChanged )
self . ui . staccatoOn . editingFinished . connect ( self . dataChanged )
self . ui . staccatoOff . editingFinished . connect ( self . dataChanged )
self . ui . tenutoOn . editingFinished . connect ( self . dataChanged )
self . ui . tenutoOff . editingFinished . connect ( self . dataChanged )
self . ui . legatoOn . editingFinished . connect ( self . dataChanged )
self . ui . legatoOff . editingFinished . connect ( self . dataChanged )
#Tab 2: Dynamics
self . ui . dynamics_ppppp . valueChanged . connect ( self . dataChanged )
self . ui . dynamics_pppp . valueChanged . connect ( self . dataChanged )
self . ui . dynamics_ppp . valueChanged . connect ( self . dataChanged )
self . ui . dynamics_pp . valueChanged . connect ( self . dataChanged )
self . ui . dynamics_p . valueChanged . connect ( self . dataChanged )
self . ui . dynamics_mp . valueChanged . connect ( self . dataChanged )
self . ui . dynamics_mf . valueChanged . connect ( self . dataChanged )
self . ui . dynamics_f . valueChanged . connect ( self . dataChanged )
self . ui . dynamics_ff . valueChanged . connect ( self . dataChanged )
self . ui . dynamics_fff . valueChanged . connect ( self . dataChanged )
self . ui . dynamics_ffff . valueChanged . connect ( self . dataChanged )
self . ui . dynamics_custom . valueChanged . connect ( self . dataChanged )
self . ui . dynamics_tacet . valueChanged . connect ( self . dataChanged )
self . ui . dynamics_fp . valueChanged . connect ( self . dataChanged )
self . ui . dynamics_sp . valueChanged . connect ( self . dataChanged )
self . ui . dynamics_spp . valueChanged . connect ( self . dataChanged )
self . ui . dynamics_sfz . valueChanged . connect ( self . dataChanged )
self . ui . dynamics_sf . valueChanged . connect ( self . dataChanged )
self . ui . dynamics_sff . valueChanged . connect ( self . dataChanged )
self . ui . buttonResetDurations . clicked . connect ( lambda : api . resetDuationSettingsSignature ( self . trackExportObject [ " id " ] ) )
self . ui . buttonResetDynamics . clicked . connect ( lambda : api . resetDynamicSettingsSignature ( self . trackExportObject [ " id " ] ) )
#Connect the hide checkbox
self . ui . advanced . toggled . connect ( self . advancedToggled )
self . ui . advanced . setChecked ( False )
def visibleToggled ( self , signal ) :
assert signal == bool ( self . ui . visibleCheckbox . checkState ( ) )
if signal :
api . unhideTrack ( self . trackExportObject [ " id " ] )
else :
api . hideTrack ( self . trackExportObject [ " id " ] )
def audibleToggled ( self , signal ) :
assert signal == bool ( self . ui . audibleCheckbox . checkState ( ) )
api . trackAudible ( self . trackExportObject [ " id " ] , signal )
def doubleTrackToggled ( self , signal ) :
api . setDoubleTrack ( self . trackExportObject [ " id " ] , signal )
def advancedToggled ( self , bool ) :
assert bool == self . ui . advanced . isChecked ( )
if bool :
self . ui . advancedContent . show ( )
else :
self . ui . advancedContent . hide ( )
@contextmanager
def blockUiSignals ( self ) :
""" prevent loops by blocking all signals that react on changed states.
Revoked at the end of the calling function .
Strictly this is not needed for signals like . clicked ( ) ( compared to . toggled ( ) ) but it is
a good last line of defense to prevent future bugs . Those signal - loops are hard to track .
ADD ALL NEW SUB WIDGETS HERE ! ! ! ! ! THIS HAPPENED TWICED ALREADY AND WAS A NIGHTMARE TO DEBUG !
SIMPLY USING self . blockSignals ( True ) HAS NO EFFECT BECAUSE THOSE ARE IN . ui .
self . setUpdatesEnabled ( False ) doesn ' t work as well.
self . children ( ) does not return the right children either , self . ui ( ) is a python object ,
not a qt widget . """
for widget in self . ui . __dict__ . values ( ) :
widget . blockSignals ( True )
for checkbox in self . ccChannels . values ( ) :
checkbox . blockSignals ( True )
yield
for widget in self . ui . __dict__ . values ( ) :
widget . blockSignals ( False )
for checkbox in self . ccChannels . values ( ) :
checkbox . blockSignals ( False )
def callClickWidgetForUpbeat ( self ) :
dialog = TickWidget ( self . parentDataEditor , initValue = self . ui . upbeatSpinBox . value ( ) )
self . ui . upbeatSpinBox . setValue ( dialog . ui . ticks . value ( ) )
def nameChanged ( self ) :
""" When enter is pressed or focus is lost """
t = self . ui . nameLineEdit . text ( )
name = self . ui . instrumentName . text ( )
short = self . ui . shortInstrumentName . text ( )
api . setTrackName ( self . trackExportObject [ " id " ] , nameString = t , initialInstrumentName = name , initialShortInstrumentName = short )
def upbeatChanged ( self ) :
""" When enter is pressed or focus is lost """
v = self . ui . upbeatSpinBox . value ( )
api . setTrackUpbeat ( self . trackExportObject [ " id " ] , v )
def dataChanged ( self ) :
""" Our data changed. Send to Engine """
with self . blockUiSignals ( ) :
dictionary = { }
dictionary [ " initialClefKeyword " ] = self . ui . initialClef_comboBox . currentText ( )
dictionary [ " initialMidiChannel " ] = self . ui . midiChannelSpinBox . value ( ) - 1
dictionary [ " initialMidiProgram " ] = self . ui . midiProgramSpinBox . value ( )
dictionary [ " initialMidiBankMsb " ] = self . ui . midiBankMsbSpinBox . value ( )
dictionary [ " initialMidiBankLsb " ] = self . ui . midiBankLsbSpinBox . value ( )
dictionary [ " ccChannels " ] = tuple ( chanNum - 1 for chanNum , checkbox in self . ccChannels . items ( ) if checkbox . checkState ( ) ) #can be unsorted
dictionary [ " midiTranspose " ] = self . ui . midiTransposeSpinBox . value ( )
dictionary [ " duration.defaultOn " ] = self . ui . defaultOn . text ( )
dictionary [ " duration.defaultOff " ] = self . ui . defaultOff . text ( )
dictionary [ " duration.staccatoOn " ] = self . ui . staccatoOn . text ( )
dictionary [ " duration.staccatoOff " ] = self . ui . staccatoOff . text ( )
dictionary [ " duration.tenutoOn " ] = self . ui . tenutoOn . text ( )
dictionary [ " duration.tenutoOff " ] = self . ui . tenutoOff . text ( )
dictionary [ " duration.legatoOn " ] = self . ui . legatoOn . text ( )
dictionary [ " duration.legatoOff " ] = self . ui . legatoOff . text ( )
dictionary [ " dynamics.ppppp " ] = self . ui . dynamics_ppppp . value ( )
dictionary [ " dynamics.pppp " ] = self . ui . dynamics_pppp . value ( )
dictionary [ " dynamics.ppp " ] = self . ui . dynamics_ppp . value ( )
dictionary [ " dynamics.pp " ] = self . ui . dynamics_pp . value ( )
dictionary [ " dynamics.p " ] = self . ui . dynamics_p . value ( )
dictionary [ " dynamics.mp " ] = self . ui . dynamics_mp . value ( )
dictionary [ " dynamics.mf " ] = self . ui . dynamics_mf . value ( )
dictionary [ " dynamics.f " ] = self . ui . dynamics_f . value ( )
dictionary [ " dynamics.ff " ] = self . ui . dynamics_ff . value ( )
dictionary [ " dynamics.fff " ] = self . ui . dynamics_fff . value ( )
dictionary [ " dynamics.ffff " ] = self . ui . dynamics_ffff . value ( )
dictionary [ " dynamics.custom " ] = self . ui . dynamics_custom . value ( )
dictionary [ " dynamics.tacet " ] = self . ui . dynamics_tacet . value ( )
dictionary [ " dynamics.fp " ] = self . ui . dynamics_fp . value ( )
dictionary [ " dynamics.sp " ] = self . ui . dynamics_sp . value ( )
dictionary [ " dynamics.spp " ] = self . ui . dynamics_spp . value ( )
dictionary [ " dynamics.sfz " ] = self . ui . dynamics_sfz . value ( )
dictionary [ " dynamics.sf " ] = self . ui . dynamics_sf . value ( )
dictionary [ " dynamics.sff " ] = self . ui . dynamics_sff . value ( )
if not self . trackExportObject == dictionary : #checks for keys and values
api . setTrackSettings ( self . trackExportObject [ " id " ] , dictionary )
#else: no change
def updateData ( self , trackExportObject ) :
""" Receives api updates. Change GUI fields accordingly """
self . trackExportObject = trackExportObject
with self . blockUiSignals ( ) :
self . trackExportObject = trackExportObject
self . ui . upbeatSpinBox . setValue ( trackExportObject [ " upbeatInTicks " ] )
self . ui . nameLineEdit . setText ( trackExportObject [ " name " ] )
self . ui . initialClef_comboBox . setCurrentIndex ( LIST_OF_CLEF_KEYWORDS . index ( trackExportObject [ " initialClefKeyword " ] ) )
self . ui . midiChannelSpinBox . setValue ( trackExportObject [ " initialMidiChannel " ] + 1 )
self . ui . midiProgramSpinBox . setValue ( trackExportObject [ " initialMidiProgram " ] )
self . ui . midiBankMsbSpinBox . setValue ( trackExportObject [ " initialMidiBankMsb " ] )
self . ui . midiBankLsbSpinBox . setValue ( trackExportObject [ " initialMidiBankLsb " ] )
self . ui . midiTransposeSpinBox . setValue ( trackExportObject [ " midiTranspose " ] )
self . ui . instrumentName . setText ( trackExportObject [ " initialInstrumentName " ] )
self . ui . shortInstrumentName . setText ( trackExportObject [ " initialShortInstrumentName " ] )
self . ui . defaultOn . setText ( trackExportObject [ " duration.defaultOn " ] )
self . ui . defaultOff . setText ( trackExportObject [ " duration.defaultOff " ] )
self . ui . staccatoOn . setText ( trackExportObject [ " duration.staccatoOn " ] )
self . ui . staccatoOff . setText ( trackExportObject [ " duration.staccatoOff " ] )
self . ui . tenutoOn . setText ( trackExportObject [ " duration.tenutoOn " ] )
self . ui . tenutoOff . setText ( trackExportObject [ " duration.tenutoOff " ] )
self . ui . legatoOn . setText ( trackExportObject [ " duration.legatoOn " ] )
self . ui . legatoOff . setText ( trackExportObject [ " duration.legatoOff " ] )
self . ui . dynamics_ppppp . setValue ( trackExportObject [ " dynamics.ppppp " ] )
self . ui . dynamics_pppp . setValue ( trackExportObject [ " dynamics.pppp " ] )
self . ui . dynamics_ppp . setValue ( trackExportObject [ " dynamics.ppp " ] )
self . ui . dynamics_pp . setValue ( trackExportObject [ " dynamics.pp " ] )
self . ui . dynamics_p . setValue ( trackExportObject [ " dynamics.p " ] )
self . ui . dynamics_mp . setValue ( trackExportObject [ " dynamics.mp " ] )
self . ui . dynamics_mf . setValue ( trackExportObject [ " dynamics.mf " ] )
self . ui . dynamics_f . setValue ( trackExportObject [ " dynamics.f " ] )
self . ui . dynamics_ff . setValue ( trackExportObject [ " dynamics.ff " ] )
self . ui . dynamics_fff . setValue ( trackExportObject [ " dynamics.fff " ] )
self . ui . dynamics_ffff . setValue ( trackExportObject [ " dynamics.ffff " ] )
self . ui . dynamics_custom . setValue ( trackExportObject [ " dynamics.custom " ] )
self . ui . dynamics_tacet . setValue ( trackExportObject [ " dynamics.tacet " ] )
self . ui . dynamics_fp . setValue ( trackExportObject [ " dynamics.fp " ] )
self . ui . dynamics_sp . setValue ( trackExportObject [ " dynamics.sp " ] )
self . ui . dynamics_spp . setValue ( trackExportObject [ " dynamics.spp " ] )
self . ui . dynamics_sfz . setValue ( trackExportObject [ " dynamics.sfz " ] )
self . ui . dynamics_sf . setValue ( trackExportObject [ " dynamics.sf " ] )
self . ui . dynamics_sff . setValue ( trackExportObject [ " dynamics.sff " ] )
if trackExportObject [ " initialMidiProgram " ] > = 0 :
self . ui . midiBankMsbSpinBox . setEnabled ( True )
self . ui . midiBankLsbSpinBox . setEnabled ( True )
else :
self . ui . midiBankMsbSpinBox . setEnabled ( False )
self . ui . midiBankLsbSpinBox . setEnabled ( False )
#hidden position can be 0 as well. So don't bool, check for None explicitely.
if trackExportObject [ " hiddenPosition " ] is None : #visible
self . ui . visibleCheckbox . setChecked ( True )
self . ui . deleteButton . setEnabled ( True )
else :
self . ui . visibleCheckbox . setChecked ( False )
self . ui . deleteButton . setEnabled ( False ) #You cannot delete hidden tracks. see api.deleteTrack
self . ui . doubleTrackCheckbox . setChecked ( trackExportObject [ " double " ] )
self . ui . audibleCheckbox . setChecked ( trackExportObject [ " audible " ] )
for i in range ( 0 , 16 ) : #without 16
#Engine from 0 to 15, GUI from 1 to 16.
self . ccChannels [ i + 1 ] . setChecked ( i in trackExportObject [ " ccChannels " ] ) #a checkbox widget.
class TrackEditor ( QtWidgets . QWidget ) :
""" Created by ControlMainWindow. One permanent instance """
def __init__ ( self , mainWindow ) :
super ( ) . __init__ ( )
self . mainWindow = mainWindow
self . layout = QtWidgets . QVBoxLayout ( )
self . layout . setAlignment ( QtCore . Qt . AlignTop | QtCore . Qt . AlignLeft )
self . setLayout ( self . layout )
api . callbacks . tracksChangedIncludingHidden . append ( self . updateTrackWidgets )
api . callbacks . updateBlockTrack . append ( self . updateBlockList )
self . tracks = { } #id:trackWidget. As any dict, not in order
#Add Track Button
self . addTrackButton = QtWidgets . QPushButton ( " add track placeholder text " )
self . addTrackButton . clicked . connect ( api . newEmptyTrack )
self . layout . addWidget ( self . addTrackButton )
self . addTenTracksButton = QtWidgets . QPushButton ( " add ten tracks placeholder text " )
self . addTenTracksButton . clicked . connect ( self . addTenTracks )
self . layout . addWidget ( self . addTenTracksButton )
#Upbeat Tick Widget and Action-Button
allUpbeats = QtWidgets . QWidget ( )
allUpbeatsLayout = QtWidgets . QHBoxLayout ( )
allUpbeatsLayout . setAlignment ( QtCore . Qt . AlignTop | QtCore . Qt . AlignLeft )
allUpbeats . setLayout ( allUpbeatsLayout )
self . layout . addWidget ( allUpbeats )
self . allUpbeatsCombinedTickWidget = CombinedTickWidget ( mainWindow )
allUpbeatsPushButton = QtWidgets . QPushButton ( translate ( " trackEditorPythonFile " , " Set all Upbeats " ) )
allUpbeatsPushButton . clicked . connect ( self . setAllUpbeats ) #gets the value itself
allUpbeatsLayout . addWidget ( self . allUpbeatsCombinedTickWidget )
allUpbeatsLayout . addWidget ( allUpbeatsPushButton )
#Set All Key Signature and Metrical Sig Buttons
#We cannot use the simple keysig submenu because we have no cursor.
customKeySigButton = QtWidgets . QPushButton ( translate ( " trackEditorPythonFile " , " Key Signatures " ) )
customKeySigButton . clicked . connect ( self . setAllCustomKeySig )
allUpbeatsLayout . addWidget ( customKeySigButton )
metricalInstructionButton = QtWidgets . QPushButton ( translate ( " trackEditorPythonFile " , " Metrical Instructions " ) )
metricalInstructionButton . clicked . connect ( self . setAllMetricalInstruction )
allUpbeatsLayout . addWidget ( metricalInstructionButton )
#Reset all Advanced Views
foldAllAdvanvced = QtWidgets . QPushButton ( translate ( " trackEditorPythonFile " , " Fold all Advanced " ) )
foldAllAdvanvced . clicked . connect ( self . foldAllAdvanvced )
allUpbeatsLayout . addWidget ( foldAllAdvanvced )
unfoldAllAdvanvced = QtWidgets . QPushButton ( translate ( " trackEditorPythonFile " , " Unfold all Advanced " ) )
unfoldAllAdvanvced . clicked . connect ( self . unfoldAllAdvanced )
allUpbeatsLayout . addWidget ( unfoldAllAdvanvced )
def addTenTracks ( self ) :
for i in range ( 10 ) :
api . newEmptyTrack ( )
def updateTrackWidgets ( self , listOfStaticTrackRepresentations ) :
""" React to the backend adding, deleting, hiding or moving tracks.
A track widget persists until the track gets deleted . No re - creation on every change . """
leftOver = list ( self . tracks . keys ( ) )
visibleTracks = [ ]
trackWidgetsInOrder = [ ]
nameLineEditsInOrder = [ ]
upbeatSpinBoxsInOrder = [ ]
for trackExportObject in listOfStaticTrackRepresentations :
#First are all normal, visible tracks.
#After that the tracks have a property "hiddenPosition" for hidden tracks that need individual sorting.
if not trackExportObject [ " id " ] in self . tracks : #A brand new track
widget = TrackWidget ( self , trackExportObject )
self . tracks [ trackExportObject [ " id " ] ] = widget
self . layout . insertWidget ( - 1 , widget )
widget . setTitle ( f ' Track { trackExportObject [ " index " ] + 1 } - id: { trackExportObject [ " id " ] } ' )
else :
leftOver . remove ( trackExportObject [ " id " ] )
self . tracks [ trackExportObject [ " id " ] ] . ui . deleteButton . setEnabled ( True )
self . tracks [ trackExportObject [ " id " ] ] . blockSignals ( True )
self . tracks [ trackExportObject [ " id " ] ] . updateData ( trackExportObject ) #This has a sideeffect of updating widgets, which will change backend data and trigger a recursive updateTrackWidgets. We need to block the signals. self.tracks has the track widgets.
self . tracks [ trackExportObject [ " id " ] ] . blockSignals ( False )
nameLineEditsInOrder . append ( self . tracks [ trackExportObject [ " id " ] ] . ui . nameLineEdit )
trackWidgetsInOrder . append ( self . tracks [ trackExportObject [ " id " ] ] )
if trackExportObject [ " hiddenPosition " ] is None :
visibleTracks . append ( trackExportObject )
numberOfTracks = trackExportObject [ " index " ] + 1
self . addTrackButton . setText ( translate ( " trackEditorPythonFile " , " Add new Track (currently: " ) + str ( numberOfTracks ) + " ) " )
self . addTenTracksButton . setText ( translate ( " trackEditorPythonFile " , " Add 10 new Tracks (currently: " ) + str ( numberOfTracks ) + " ) " )
for trId in leftOver : #track still exist here but not in the backend. Delete.
w = self . tracks [ trId ]
del self . tracks [ trId ]
w . setParent ( None )
w . close ( )
#Sort tracks as they are in the backend/score-view
for widget in trackWidgetsInOrder : #visibleTracks is sorted and does not have empty tracks.
self . layout . removeWidget ( widget )
for widget in trackWidgetsInOrder :
#Re-Insert all, this time in order.
self . layout . insertWidget ( - 1 , widget )
#Create tab order. Those work in pairs, linked list. a before b. And multiple of those pairs need to be in order itself.
if len ( nameLineEditsInOrder ) > 1 :
for pair in pairwise ( nameLineEditsInOrder ) :
self . setTabOrder ( * pair ) #takes exactly two arguments
if len ( visibleTracks ) == 1 :
w = self . tracks [ visibleTracks [ 0 ] [ " id " ] ]
w . ui . deleteButton . setEnabled ( False ) #it is not possible to delete the only visible track. The backend will prevent it but we don't even offer the choice here.
def updateBlockList ( self , trackId , listOfBlockExportDicts ) :
""" This comes in for every small item change. But only per track """
trackWidget = self . tracks [ trackId ]
prefix = translate ( " trackEditorPythonFile " , " Block Order: " ) + " "
trackWidget . ui . blocksLabel . setText ( prefix + " , " . join ( block [ " name " ] for block in listOfBlockExportDicts ) )
def setAllUpbeats ( self ) :
for trackWidget in self . tracks . values ( ) :
trackWidget . ui . upbeatSpinBox . setValue ( self . allUpbeatsCombinedTickWidget . value ( ) )
def foldAllAdvanvced ( self ) :
for trackWidget in self . tracks . values ( ) :
trackWidget . ui . advanced . setChecked ( False )
def unfoldAllAdvanced ( self ) :
for trackWidget in self . tracks . values ( ) :
trackWidget . ui . advanced . setChecked ( True )
def setAllCustomKeySig ( self ) :
CustomKeySignatureWidget ( self . mainWindow , setInitialKeysigInsteadCursorInsert = True )
def setAllMetricalInstruction ( self ) :
SecondaryMetricalInstructionMenu ( self . mainWindow , setInitialInsteadCursorInsert = True ) . exec ( )
"""
api . callbacks . updateBlockTrack . append ( lambda trId , blocksExportData : self . tracks [ trId ] . blockScene . regenerateBlocks ( blocksExportData ) )
class TrackBlockScene ( QtWidgets . QGraphicsScene ) :
def __init__ ( self , parentWidget ) :
super ( ) . __init__ ( )
self . blocks = [ ]
self . parentWidget = parentWidget
def mousePressEvent ( self , event ) :
button = event . button ( )
if button == 1 : #left
self . setEnabled ( False )
super ( ) . mousePressEvent ( event )
def mouseReleaseEvent ( self , event ) :
button = event . button ( )
if button == 1 : #left
self . setEnabled ( True )
super ( ) . mouseReleaseEvent ( event )
def regenerateBlocks ( self , blocksExportData ) :
self . clear ( )
self . blocks = [ ]
dur = 0
for backendBlock in blocksExportData :
b = BlockGraphicsItem ( self , backendBlock )
self . blocks . append ( b )
self . addItem ( b )
b . setPos ( backendBlock [ " tickindex " ] / constantsAndConfigs . blocksTicksToPixelRatio , 0 )
dur + = backendBlock [ " completeDuration " ]
self . parentWidget . ui . blocks . setFixedSize ( dur / constantsAndConfigs . blocksTicksToPixelRatio , 30 )
class BlockGraphicsItem ( QtWidgets . QGraphicsRectItem ) :
def __init__ ( self , parentTrackWidget , blockExportObject ) :
width = blockExportObject [ " completeDuration " ] / constantsAndConfigs . blocksTicksToPixelRatio
super ( ) . __init__ ( 0 , 0 , width , 30 ) #x, y, w, h
self . setBrush ( self . stringToColor ( blockExportObject [ " name " ] ) )
#self.setFlags(QtWidgets.QGraphicsItem.ItemSendsGeometryChanges|QtWidgets.QGraphicsItem.ItemIsFocusable)
#self.setCursor(QtCore.Qt.SizeAllCursor)
def stringToColor ( self , st ) :
if st :
c = md5 ( st . encode ( ) ) . hexdigest ( )
return QtGui . QColor ( int ( c [ 0 : 9 ] , 16 ) % 255 , int ( c [ 10 : 19 ] , 16 ) % 255 , int ( c [ 20 : 29 ] , 16 ) % 255 , 255 )
else :
return QtGui . QColor ( 255 , 255 , 255 , 255 ) #Return White
"""