from.itemsimport*#loading from file needs all items.
classBlock(object):
#allBlocks = WeakValueDictionary() #key is the blockId, value is the weak reference to the Block
allBlocks={}#key is the blockId, value is the Block. This is a one way dict. It gets never deleted so undo can recover old blocks. Since blocks are unique this is no problem.
#NEVER!! iteratre over allBlocks if you don't know what you are doing. This contains deleted blocks as well.
firstBlockWithNewContentDuringDeserializeToObject=dict()#this is not resetted anywhere since each load is a program start.
def__init__(self,track):
self.data=list()
self.name=str(id(self))
self._minimumInTicks=[0]# if this is bigger than the actual duration-sum of the content this will be used instead. Advice: best used in the form of x*210 (multiple of real base-durations) #is content linked, thats why it is a mutable list of len==1.
self.linkedContentBlocks=WeakSet()#only new standalone blocks use this empty WeakSet. once you contentLink a block it will be overwritten.
self._secondInit(parentTrack=track)
def_secondInit(self,parentTrack):
"""see Score._secondInit"""
self._parentTrack=weakref_ref(parentTrack)#a block can only be in one track.
ifserializedObject["data"]isNone:#Found a content linked block which already has one member of its group in the score
firstBlock=Block.firstBlockWithNewContentDuringDeserializeToObject[serializedObject["contentLinkGroup"]]#block with the same contentGroup. This is the one with the real data.
self.data=firstBlock.data
self._minimumInTicks=firstBlock._minimumInTicks
self.linkedContentBlocks=firstBlock.linkedContentBlocks#add self to this is in _secondInit
else:#found a stand-alone block or the first one of a content link group
else:#content linked, but not the first. Block already serialized.
result["data"]=None#we don't need to do anything more. The rest is handled by load and instanceFromSerializedData
break
#else:
#loop ran through. This never happens.
returnresult
defrememberBlock(self):
oid=id(self)
Block.allBlocks[oid]=self#This is on the score level, or a global level. That means we don't need to change this, even if the track gets moved to a new track by the api.
"""The first block might have an upbeat. We check that here and not in the track."""
actualBlockDuration=0
foriteminself.data:
actualBlockDuration+=item.logicalDuration()
ifactualBlockDuration>=self.minimumInTicks:
returnactualBlockDuration
else:
returnself.minimumInTicks
defstaticExportEndMarkerDuration(self):
actualBlockDuration=0
foriteminself.data:
actualBlockDuration+=item.logicalDuration()
ifactualBlockDuration>=self.minimumInTicks:#this also guarantees that the substraction below is > 0.
return0
else:
returnself.minimumInTicks-actualBlockDuration#this is the difference to self.duration()
defposition(self):
"""the position of the subcursor in this block"""
returnself.localCursorIndex
defleft(self):
ifself.localCursorIndex>0:
self.localCursorIndex-=1
returnTrue
else:
returnFalse#Already at the start.
defright(self):
ifnotself.isAppending():
self.localCursorIndex+=1
returnTrue
else:
returnFalse#Already at the end.
defhead(self):
self.localCursorIndex=0
deftail(self):
self.localCursorIndex=len(self.data)#eventhough len counts from 1 and the cursorIndex from 0 we want exactly to be after the last item in data.
defgoToItem(self,itemInstance):
ifitemInstance:
self.head()
whileself.right():
item=self.currentItem()
ifitemisitemInstance:
returnTrue
else:
raiseValueError("Item not in this block. ",self,itemInstance)
elifitemInstanceisNone:
self.tail()
else:
raiseValueError("You must go to an item or None, not to: ",itemInstance)
defcurrentItem(self):
"""Can be used with goToItem"""
ifnotself.isAppending():
returnself.data[self.localCursorIndex]
else:
returnNone
defpreviousItem(self):
"""Can be used with goToItem"""
ifself.localCursorIndex>0:
returnself.data[self.localCursorIndex-1]
else:
returnNone
defnextItem(self):
"""Can be used with goToItem"""
ifself.localCursorIndex+1>=len(self.data):#one before appending or appending itself
returnNone
else:
returnself.data[self.localCursorIndex+1]
definsert(self,item):
self.data.insert(self.localCursorIndex,item)#we do not need to check if appending or not. list.insert appends if the index is higher then len()
#self.localCursorIndex += 1 #we don't need to go right here because track.insert() is calling its own right() directly after insert, which triggers block.right()
#we don't need to delete self from item.parentBlocks. It is automatically deleted in all contentLinked blocks, of course including its parentBlocks WeakSet()
returnresult
defisAppending(self):
ifself.localCursorIndex==len(self.data):#len counts from 1 and the cursorIndex from 0. So if they are the same we are one cursor position right of last item
returnTrue
else:
returnFalse
deflilypond(self):
"""Called by track.lilypond(), returns a string"""