Browse Source

add initial area for signatures

master
Nils 2 years ago
parent
commit
4d1e5646a5
  1. 1
      CHANGELOG
  2. 57
      engine/api.py
  3. 2
      engine/lilypond.py
  4. 2
      engine/main.py
  5. 59
      engine/track.py
  6. 17
      qtgui/customkeysignature.py
  7. 13
      qtgui/items.py
  8. 5
      qtgui/mainwindow.py
  9. 37
      qtgui/musicstructures.py
  10. 8
      qtgui/scorescene.py
  11. 10
      qtgui/submenus.py
  12. 35
      qtgui/trackEditor.py
  13. 2
      template/qtgui/mainwindow.py

1
CHANGELOG

@ -7,6 +7,7 @@ External contributors notice at the end of the line: (LastName, FirstName / nick
## 2022-07-15 2.1.0 ## 2022-07-15 2.1.0
New function: custom key signature for any combination. New function: custom key signature for any combination.
Add area in the GUI to set initial key signature, metrical instructions and clefs, accessed through the track editor
Add two new functions to paste directly transposed (modal and real transposition) Add two new functions to paste directly transposed (modal and real transposition)
When not in F4-StepInput Mode use midi keyboard as pitch-cursor. When not in F4-StepInput Mode use midi keyboard as pitch-cursor.
Add midi-in selector drop down, as seen in Tembro and Fluajho. Add midi-in selector drop down, as seen in Tembro and Fluajho.

57
engine/api.py

@ -628,6 +628,12 @@ def newEmptyTrack():
"""Append an empty track and switch to the new track""" """Append an empty track and switch to the new track"""
newIndex = len(session.data.tracks) newIndex = len(session.data.tracks)
newTrack = Track(session.data) newTrack = Track(session.data)
if newIndex > 0:
newTrack.initialKeySignature = session.data.tracks[0].initialKeySignature.copy()
newTrack.initialMetricalInstruction = session.data.tracks[0].initialMetricalInstruction.copy()
newTrack.upbeatInTicks = session.data.tracks[0].upbeatInTicks #just an int
insertTrack(newIndex, newTrack) #handles callbacks and undo insertTrack(newIndex, newTrack) #handles callbacks and undo
return (id(newTrack)) return (id(newTrack))
@ -1851,6 +1857,20 @@ def insertKeySignature(root:int, scheme:list, lilypondOverride:str=None):
keysig.lilypondParameters["override"] = lilypondOverride keysig.lilypondParameters["override"] = lilypondOverride
insertItem(keysig) insertItem(keysig)
def setInitialKeySignatures(root:int, scheme:list, lilypondOverride:str=None):
"""Variation of insertKeySignature to set the initial key sig for ALL tracks"""
keysig = items.KeySignature(root, scheme)
if lilypondOverride:
keysig.lilypondParameters["override"] = lilypondOverride
for track in session.data.tracks:
track.initialKeySignature = keysig.copy()
callbacks._tracksChanged()
for trId in session.data.listOfTrackIds():
callbacks._updateTrack(trId) #create content: music items
callbacks._setCursor() #In case we have a different accidental for the pitch cursor
def commonKeySignaturesAsList(): def commonKeySignaturesAsList():
"""For the GUIs convenience so they can populate their """For the GUIs convenience so they can populate their
drop down lists. drop down lists.
@ -1928,6 +1948,23 @@ def insertMetricalInstruction(treeOfInstructions, lilypondOverride = None):
item.lilypondParameters["override"] = lilypondOverride item.lilypondParameters["override"] = lilypondOverride
insertItem(item) insertItem(item)
def setInitialMetricalInstruction(treeOfInstructions, lilypondOverride:str=None):
"""Variation of insertMetricalInstruction to set the initial one for ALL tracks"""
item = items.MetricalInstruction(treeOfInstructions)
if lilypondOverride:
item.lilypondParameters["override"] = lilypondOverride
for track in session.data.tracks:
track.initialMetricalInstruction = item.copy()
callbacks._tracksChanged()
for trId in session.data.listOfTrackIds():
callbacks._updateTrack(trId) #create content: music items
callbacks._setCursor() #In case we have a different accidental for the pitch cursor
def commonMetricalInstructionsAsList(): def commonMetricalInstructionsAsList():
"""for musical reasons 5/4 and 7/8 and other a-symetrical """for musical reasons 5/4 and 7/8 and other a-symetrical
metrical instructions cannot be in here since it is unknown which metrical instructions cannot be in here since it is unknown which
@ -1946,7 +1983,7 @@ def commonMetricalInstructionsAsList():
"3/2", "3/2",
] ]
def insertCommonMetricalInstrucions(scheme): def insertCommonMetricalInstrucions(scheme, setInitialInsteadCursorInsert=False):
"""A metrical instruction requires a lilypond override. You can use them without of course but """A metrical instruction requires a lilypond override. You can use them without of course but
they will not work when exported.""" they will not work when exported."""
schemes = { schemes = {
@ -1973,7 +2010,11 @@ def insertCommonMetricalInstrucions(scheme):
"1/1" : "\\cadenzaOff \\time 1/1", "1/1" : "\\cadenzaOff \\time 1/1",
"3/2" : "\\cadenzaOff \\time 3/2", "3/2" : "\\cadenzaOff \\time 3/2",
} }
insertMetricalInstruction(schemes[scheme], lilypondOverride = lilypond[scheme])
if setInitialInsteadCursorInsert:
setInitialMetricalInstruction(schemes[scheme], lilypondOverride = lilypond[scheme])
else: #item insert
insertMetricalInstruction(schemes[scheme], lilypondOverride = lilypond[scheme])
def metricalTest(): def metricalTest():
@ -2723,7 +2764,7 @@ def lilypondText(text:str):
try: try:
insertItem(items.LilypondText(text)) insertItem(items.LilypondText(text))
except Exception as err: except Exception as err:
logger.error(err) logger.error(f"Text Item Lilypond Error for '{text}': " + err)
def lilypondMark(text:str, aboveStaff=True): def lilypondMark(text:str, aboveStaff=True):
"""This is actual human-readable text, called a Mark by lilypond. """This is actual human-readable text, called a Mark by lilypond.
@ -2747,16 +2788,18 @@ def lilypondMark(text:str, aboveStaff=True):
def exportLilypond(absoluteFilePath): def exportLilypond(absoluteFilePath):
save() save()
lilypond.saveAsLilypond(session.data, absoluteFilePath) lilypond.saveAsLilypond(session.data, absoluteFilePath)
#try: try:
# lilypond.saveAsLilypond(session.data, absoluteFilePath) lilypond.saveAsLilypond(session.data, absoluteFilePath)
#except Exception as err: except Exception as err:
# logger.error(err) logger.error(err)
def showPDF(): def showPDF():
try: try:
save() save()
lilypond.saveAsLilypondPDF(session.data, openPDF = True) lilypond.saveAsLilypondPDF(session.data, openPDF = True)
except Exception as err: except Exception as err:
logger.error("PDF/Lilypond Exception. The following is a PYTHON, not a lilypond error:")
print (err)
logger.error(err) logger.error(err)

2
engine/lilypond.py

@ -41,7 +41,7 @@ da = date.fromordinal(730920) # 730920th day after 1. 1. 0001
def saveAsLilypond(score, absoluteFilePath = None): def saveAsLilypond(score, absoluteFilePath = None):
#absoluteFilePath = self.absoluteFilePath + ".ly" if (not absoluteFilePath) else absoluteFilePath #absoluteFilePath = self.absoluteFilePath + ".ly" if (not absoluteFilePath) else absoluteFilePath
assert absoluteFilePath.endswith(".ly") assert absoluteFilePath.endswith(".ly"), absoluteFilePath
result = score.lilypond() result = score.lilypond()
if result: if result:
with open(absoluteFilePath, "w", encoding="utf-8") as f: with open(absoluteFilePath, "w", encoding="utf-8") as f:

2
engine/main.py

@ -1056,7 +1056,7 @@ class Data(template.engine.sequencer.Score):
Only visible tracks get exported. Only visible tracks get exported.
Each object controls what it exports. Overrides are possible at every level. Each object controls what it exports. Overrides are possible at every level.
""" """
tempoStaff = self.tempoTrack.lilypond() if self.metaData["metronome"] else "" tempoStaff = self.tempoTrack.lilypond() if "metronome" in self.metaData and self.metaData["metronome"] else ""
data = {track:track.lilypond() for track in self.tracks} #processed in the lilypond module data = {track:track.lilypond() for track in self.tracks} #processed in the lilypond module
return fromTemplate(session = self.parentSession, templateFile = "default.ly", data = data, meta = self.metaData, tempoStaff = tempoStaff) return fromTemplate(session = self.parentSession, templateFile = "default.ly", data = data, meta = self.metaData, tempoStaff = tempoStaff)

59
engine/track.py

@ -273,10 +273,7 @@ class TrackState(object):
"""TrackState stays not the same for one track but gets recreated, at least in track.head(). """TrackState stays not the same for one track but gets recreated, at least in track.head().
This means init is always position 0.""" This means init is always position 0."""
defaultClef = Clef("treble")
defaultDynamicSignature = DynamicSignature("custom") #This is the start dynamic value like 'forte', not the DynamicSettingsSignature defaultDynamicSignature = DynamicSignature("custom") #This is the start dynamic value like 'forte', not the DynamicSettingsSignature
defaultMetricalInstruction = MetricalInstruction(tuple(), isMetrical = False)
defaultKeySignature = KeySignature(20, [0,0,0,0,0,0,0])
def __init__(self, track): def __init__(self, track):
self.track = track self.track = track
@ -286,10 +283,9 @@ class TrackState(object):
self.ticksSinceLastMeasureStart = 0 #Only for export. self.ticksSinceLastMeasureStart = 0 #Only for export.
self.barlines = [] #a list of ints/tickpositions. These are the non-editable, non-movable single barlines. self.barlines = [] #a list of ints/tickpositions. These are the non-editable, non-movable single barlines.
#stacks. There is always one item left in the stack, the default: #stacks. There is always one item left in the stack, the default:
self.keySignatures = [self.defaultKeySignature] #C Major self.keySignatures = [track.initialKeySignature]
#self.clefs = [self.defaultClef]
self.clefs = [Clef(track.initialClefKeyword)] self.clefs = [Clef(track.initialClefKeyword)]
self.metricalInstructions = [self.defaultMetricalInstruction] #no barlines, no metrical information. self.metricalInstructions = [track.initialMetricalInstruction] #no barlines, no metrical information.
self.dynamicSignatures = [self.defaultDynamicSignature] self.dynamicSignatures = [self.defaultDynamicSignature]
self.dynamicRamp = None #Not for cursor movement so it is not a stack. self.dynamicRamp = None #Not for cursor movement so it is not a stack.
self.EXPORTtiedNoteExportObjectsWaitingForClosing = {} #pitch:noteExportObject . Empty during cursor movement, filled during export. self.EXPORTtiedNoteExportObjectsWaitingForClosing = {} #pitch:noteExportObject . Empty during cursor movement, filled during export.
@ -401,9 +397,8 @@ class Track(object):
#Since version 2.1.0 the initial signature can be set by the user, mostly as a GUI convenience. #Since version 2.1.0 the initial signature can be set by the user, mostly as a GUI convenience.
#These are used by the track-state init/head #These are used by the track-state init/head
self.initialClefKeyword:str = "treble" #it is much easier to handle the keyword with save/load and setting this value, so we are not using the item.Clef class self.initialClefKeyword:str = "treble" #it is much easier to handle the keyword with save/load and setting this value, so we are not using the item.Clef class
self.initialKeySignature = KeySignature(20, [0,0,0,0,0,0,0]) #C Major
#self.initialMetricalInstruction = MetricalInstruction(tuple(), isMetrical = False) self.initialMetricalInstruction = MetricalInstruction(tuple(), isMetrical = False)
#self.initialKeySignature = KeySignature(20, [0,0,0,0,0,0,0])
self.asMetronomeData = None #This track as metronome version. Is always up to date through export. self.asMetronomeData = None #This track as metronome version. Is always up to date through export.
@ -842,21 +837,36 @@ class Track(object):
This dict begins here. Each track gets its own. This dict begins here. Each track gets its own.
""" """
carryLilypondRanges = {} #handed from item to item for ranges such as tuplets. Can act like a stack or simply remember stuff.
#Initial Metrical Instruction
for item in self.blocks[0].data[:4]: for item in self.blocks[0].data[:4]:
if type(item) is MetricalInstruction: if type(item) is MetricalInstruction: #don't use the initial. use the user provided one.
timeSig = "" timeSig = ""
break break
else: else:
timeSig = "\\once \\override Staff.TimeSignature #'stencil = ##f \\cadenzaOn\n" if self.initialMetricalInstruction.treeOfInstructions:
timeSig = self.initialMetricalInstruction.lilypond(carryLilypondRanges) + "\n"
else:
timeSig = "\\once \\override Staff.TimeSignature #'stencil = ##f \\cadenzaOn\n"
#Initial Clef
for item in self.blocks[0].data[:4]: for item in self.blocks[0].data[:4]:
if type(item) is Clef: if type(item) is Clef: #don't use the initial. use the user provided one.
clef = "" clef = ""
break break
else: else:
clef = "\\clef " + self.initialClefKeyword + "\n" #internal clef keywords are the same as lilypond clef = "\\clef " + self.initialClefKeyword + "\n" #internal clef keywords are the same as lilypond
carryLilypondRanges = {} #handed from item to item for ranges such as tuplets. Can act like a stack or simply remember stuff. #Initial Key Signature
for item in self.blocks[0].data[:4]:
if type(item) is KeySignature: #don't use the initial. use the user provided one.
keySignature = ""
break
else:
keySignature = self.initialKeySignature.lilypond(carryLilypondRanges) + "\n"
upbeatLy = "\\partial {} ".format(Duration.createByGuessing(self.upbeatInTicks).lilypond(carryLilypondRanges)) if self.upbeatInTicks else "" upbeatLy = "\\partial {} ".format(Duration.createByGuessing(self.upbeatInTicks).lilypond(carryLilypondRanges)) if self.upbeatInTicks else ""
@ -911,7 +921,7 @@ class Track(object):
pass pass
if lyData: if lyData:
return clef + timeSig + upbeatLy + lyData + "\n" return clef + keySignature + timeSig + upbeatLy + lyData + "\n"
else: else:
return "" #Empty track return "" #Empty track
@ -926,6 +936,8 @@ class Track(object):
"double" : self.double, "double" : self.double,
"initialClefKeyword" : self.initialClefKeyword, "initialClefKeyword" : self.initialClefKeyword,
"initialKeySignature" : self.initialKeySignature.serialize(),
"initialMetricalInstruction" : self.initialMetricalInstruction.serialize(),
"initialMidiChannel" : self.initialMidiChannel, "initialMidiChannel" : self.initialMidiChannel,
"initialMidiProgram" : self.initialMidiProgram, "initialMidiProgram" : self.initialMidiProgram,
"initialMidiBankMsb" : self.initialMidiBankMsb, "initialMidiBankMsb" : self.initialMidiBankMsb,
@ -947,8 +959,8 @@ class Track(object):
self.upbeatInTicks = int(serializedData["upbeatInTicks"]) self.upbeatInTicks = int(serializedData["upbeatInTicks"])
self.blocks = [Block.instanceFromSerializedData(block, parentObject = self) for block in serializedData["blocks"]] self.blocks = [Block.instanceFromSerializedData(block, parentObject = self) for block in serializedData["blocks"]]
self.durationSettingsSignature = DurationSettingsSignature.instanceFromSerializedData(serializedData["durationSettingsSignature"], parentObject = self) self.durationSettingsSignature = DurationSettingsSignature.instanceFromSerializedData(serializedData["durationSettingsSignature"], parentObject=self)
self.dynamicSettingsSignature = DynamicSettingsSignature.instanceFromSerializedData(serializedData["dynamicSettingsSignature"], parentObject = self) self.dynamicSettingsSignature = DynamicSettingsSignature.instanceFromSerializedData(serializedData["dynamicSettingsSignature"], parentObject=self)
self.double = serializedData["double"] self.double = serializedData["double"]
self.initialMidiChannel = serializedData["initialMidiChannel"] self.initialMidiChannel = serializedData["initialMidiChannel"]
self.initialMidiProgram = serializedData["initialMidiProgram"] self.initialMidiProgram = serializedData["initialMidiProgram"]
@ -966,6 +978,16 @@ class Track(object):
else: else:
self.initialClefKeyword = "treble" self.initialClefKeyword = "treble"
if "initialKeySignature" in serializedData:
self.initialKeySignature = KeySignature.instanceFromSerializedData(serializedData["initialKeySignature"], parentObject=self)
else:
self.initialKeySignature = KeySignature(20, [0,0,0,0,0,0,0]) #C Major
if "initialMetricalInstruction" in serializedData:
self.initialMetricalInstruction = MetricalInstruction.instanceFromSerializedData(serializedData["initialMetricalInstruction"], parentObject=self)
else:
self.initialMetricalInstruction = MetricalInstruction(tuple(), isMetrical = False)
self._processAfterInit() self._processAfterInit()
return self return self
@ -1254,7 +1276,10 @@ class Track(object):
metaData["barlines"] = barlines.keys() metaData["barlines"] = barlines.keys()
metaData["duration"] = self.state.tickindex #tickindex is now at the end, so this is the end duration. This includes Blocks minimumDuration as well since it is included in left/right metaData["duration"] = self.state.tickindex #tickindex is now at the end, so this is the end duration. This includes Blocks minimumDuration as well since it is included in left/right
metaData["beams"] = resultBeamGroups metaData["beams"] = resultBeamGroups
metaData["initialClef"] = self.initialClefKeyword metaData["initialClef"] = self.initialClefKeyword #it is already a string
#the state is at the end, but it doesn't matter for the init-sigs. It will show the wrong tick index, but don't worry.
metaData["initialKeySignature"] = self.initialKeySignature.exportObject(self.state)
metaData["initialMetricalInstruction"] = self.initialMetricalInstruction.exportObject(self.state)
#Notes #Notes
t = (midiNotesBinaryCboxData, 0, self.state.tickindex) t = (midiNotesBinaryCboxData, 0, self.state.tickindex)

17
qtgui/customkeysignature.py

@ -34,13 +34,21 @@ import engine.api as api
class CustomKeySignatureWidget(QtWidgets.QDialog): class CustomKeySignatureWidget(QtWidgets.QDialog):
def __init__(self, mainWindow): def __init__(self, mainWindow, setInitialKeysigInsteadCursorInsert:bool=False):
"""Init is called everytime the dialog gets shown""" """Init is called everytime the dialog gets shown.
This function can be used to insert a normal sig at cursor position, which is the main
purpose.
But later we retrofitted the option to set the initial keysig for all tracks,
which is just a different api function.
set setInitialKeysigInsteadCursorInsert to True for this behaviour.
"""
super().__init__(mainWindow) super().__init__(mainWindow)
self.mainWindow = mainWindow self.mainWindow = mainWindow
self.ui = Ui_customKeySignature() self.ui = Ui_customKeySignature()
self.ui.setupUi(self) self.ui.setupUi(self)
self.setInitialKeysigInsteadCursorInsert = setInitialKeysigInsteadCursorInsert
self.ui.buttonBox.accepted.connect(self.process) self.ui.buttonBox.accepted.connect(self.process)
self.ui.buttonBox.rejected.connect(self.reject) self.ui.buttonBox.rejected.connect(self.reject)
@ -242,6 +250,9 @@ class CustomKeySignatureWidget(QtWidgets.QDialog):
if button.isChecked(): if button.isChecked():
deviationFromMajorScale.append(10*counter -20) deviationFromMajorScale.append(10*counter -20)
api.insertKeySignature(root=selectedRootPitch, scheme=deviationFromMajorScale) if self.setInitialKeysigInsteadCursorInsert:
api.setInitialKeySignatures(root=selectedRootPitch, scheme=deviationFromMajorScale)
else: #default
api.insertKeySignature(root=selectedRootPitch, scheme=deviationFromMajorScale)
self.done(True) self.done(True)

13
qtgui/items.py

@ -668,11 +668,11 @@ class GuiKeySignature(GuiItem):
#No accidentals in the keysig (C-Major, D-Dorian etc.) gets a big natural sign. #No accidentals in the keysig (C-Major, D-Dorian etc.) gets a big natural sign.
self.bigNatural = GuiKeySignature.accidentals[0]() #"big natural"... hö hö hö hö self.bigNatural = GuiKeySignature.accidentals[0]() #"big natural"... hö hö hö hö
self.bigNatural.setParentItem(self) self.bigNatural.setParentItem(self)
self.bigNatural.setPos(constantsAndConfigs.magicPixel, constantsAndConfigs.stafflineGap * -1) self.bigNatural.setPos(constantsAndConfigs.magicPixel, constantsAndConfigs.stafflineGap * GuiKeySignature.pitchModYOffset[0])
self.rootGlyph = QtWidgets.QGraphicsSimpleTextItem(pitch.baseNotesToAccidentalNames[self.staticItem["root"]]) self.rootGlyph = QtWidgets.QGraphicsSimpleTextItem(pitch.baseNotesToAccidentalNames[self.staticItem["root"]])
self.rootGlyph.setParentItem(self) self.rootGlyph.setParentItem(self)
self.rootGlyph.setPos(constantsAndConfigs.negativeMagicPixel, 2*constantsAndConfigs.stafflineGap) self.rootGlyph.setPos(1, 2*constantsAndConfigs.stafflineGap)
accidentals = { #QGraphicsItems can only be used once so we have to save a class constructor here instead of an instance. accidentals = { #QGraphicsItems can only be used once so we have to save a class constructor here instead of an instance.
@ -765,7 +765,7 @@ class GuiTimeSignature(GuiItem):
class GuiMetricalInstruction(GuiItem): class GuiMetricalInstruction(GuiItem):
def __init__(self, staticItem): def __init__(self, staticItem, drawMarker=True):
super().__init__(staticItem) super().__init__(staticItem)
if staticItem["oneMeasureInTicks"] == 0: if staticItem["oneMeasureInTicks"] == 0:
@ -790,9 +790,10 @@ class GuiMetricalInstruction(GuiItem):
self.text.setParentItem(self) self.text.setParentItem(self)
self.text.setPos(-6, -7 * constantsAndConfigs.stafflineGap) #by definition the metrical sig is in the same position as the measure number. Shift high up self.text.setPos(-6, -7 * constantsAndConfigs.stafflineGap) #by definition the metrical sig is in the same position as the measure number. Shift high up
self.marker = GuiPositionMarker() if drawMarker:
self.marker.setParentItem(self) self.marker = GuiPositionMarker()
self.marker.setPos(0,0) self.marker.setParentItem(self)
self.marker.setPos(0,0)
def treeToPrettyString(self, tree): def treeToPrettyString(self, tree):
"""Convert a metrical instruction into a pretty string with note symbols """Convert a metrical instruction into a pretty string with note symbols

5
qtgui/mainwindow.py

@ -198,7 +198,10 @@ class MainWindow(TemplateMainWindow):
def toggleMainView(self): def toggleMainView(self):
"""Switch between the Track Editor and Score/Block Editor""" """Switch between the Track Editor and Score/Block Editor.
This can happen through the menuaction. There is another way through mouseReleaseEvent
in scoreScene, but this triggers the menu action as well.
Without menu action we don't change the checkbox."""
if self.ui.actionData_Editor.isChecked(): if self.ui.actionData_Editor.isChecked():
self.ui.mainStackWidget.setCurrentIndex(self.ui.mainStackWidget.indexOf(self.trackEditor)) self.ui.mainStackWidget.setCurrentIndex(self.ui.mainStackWidget.indexOf(self.trackEditor))

37
qtgui/musicstructures.py

@ -36,7 +36,7 @@ from template.qtgui.helper import stretchLine, stretchRect, callContextMenu, rem
import engine.api as api import engine.api as api
from . import graphs from . import graphs
from .items import staticItem2Item, GuiTieCurveGraphicsItem, GuiClef from .items import staticItem2Item, GuiTieCurveGraphicsItem, GuiClef, GuiKeySignature, GuiMetricalInstruction
from .constantsAndConfigs import constantsAndConfigs from .constantsAndConfigs import constantsAndConfigs
from .submenus import BlockPropertiesEdit from .submenus import BlockPropertiesEdit
@ -450,6 +450,34 @@ class GuiTrack(QtWidgets.QGraphicsItem):
super().__init__() super().__init__()
self.parent = parent self.parent = parent
def createInitialSignatureArea(self, metaDataDict):
"""Assumes an empty track, called only in createGraphicItemsFromData
Initial Signatures, if present. Negative X-axis values.
The stafflines draw themselves to the left by the constant sceneXOffsetForInitialSigs.
In scoreScene.mouseReleaseEvent we check if the mouse click was within our initial area
and call the track Editor."""
xPos = sceneXOffsetForInitialSigs+5
clefSVGItem = GuiClef.clefs[metaDataDict["initialClef"]]() #just the graphics
self.anchor.addToGroup(clefSVGItem)
clefSVGItem.setPos(xPos, GuiClef.cleffYOnStaff[metaDataDict["initialClef"]])
xPos += clefSVGItem.boundingRect().width()
initKeySig = GuiKeySignature(metaDataDict["initialKeySignature"])
self.anchor.addToGroup(initKeySig)
initKeySig.setPos(xPos, 0)
xPos += initKeySig.boundingRect().width()
#The metrical position is just text. We put that on top of the staff and remove the arrow indicator.
if metaDataDict["initialMetricalInstruction"]["oneMeasureInTicks"]:
initMetricalInstruction = GuiMetricalInstruction(metaDataDict["initialMetricalInstruction"], drawMarker=False)
self.anchor.addToGroup(initMetricalInstruction)
initMetricalInstruction.setPos(sceneXOffsetForInitialSigs +10, 0) #+10 because the GuiItem itself has a negative x offset
def createGraphicItemsFromData(self, staticRepresentationList): def createGraphicItemsFromData(self, staticRepresentationList):
"""Create staff objects including simple barlines""" """Create staff objects including simple barlines"""
self.parentScore.cursor.clearItemHighlight() #or else the current highlight gets deleted while it is on an item self.parentScore.cursor.clearItemHighlight() #or else the current highlight gets deleted while it is on an item
@ -463,11 +491,8 @@ class GuiTrack(QtWidgets.QGraphicsItem):
self.anchor = GuiTrack.TrackAnchor(self) self.anchor = GuiTrack.TrackAnchor(self)
self.anchor.setParentItem(self) self.anchor.setParentItem(self)
metaDataDict = staticRepresentationList.pop() metaDataDict = staticRepresentationList.pop() #we want the dict, but we also want it separated from the item list. It is, by specs, the last item.
#Initial Signatures, if present. Negative X-axis values self.createInitialSignatureArea(metaDataDict)
clefSVGItem = GuiClef.clefs[metaDataDict["initialClef"]]() #just the graphics
self.anchor.addToGroup(clefSVGItem)
clefSVGItem.setPos(sceneXOffsetForInitialSigs+5, GuiClef.cleffYOnStaff[metaDataDict["initialClef"]])
#Real items, positive X-axis values #Real items, positive X-axis values
for staticItem in staticRepresentationList: for staticItem in staticRepresentationList:

8
qtgui/scorescene.py

@ -336,9 +336,6 @@ class GuiScore(QtWidgets.QGraphicsScene):
self.updateSceneRect() self.updateSceneRect()
#Macro-Structure: Score / Track / Block Moving and Duplicating #Macro-Structure: Score / Track / Block Moving and Duplicating
#Hold the shift Key to unlock the moving mode.super().keyPressEvent(event)
#No note-editing requires a mouse action, so the mouse is free for controlling other aspects.
#Like zooming or moving blocks around.
""" """
def keyPressEvent(self, event): def keyPressEvent(self, event):
@ -601,6 +598,11 @@ class GuiScore(QtWidgets.QGraphicsScene):
else: else:
api.toTickindex(trackId, event.scenePos().x() * constantsAndConfigs.ticksToPixelRatio) api.toTickindex(trackId, event.scenePos().x() * constantsAndConfigs.ticksToPixelRatio)
elif (not event.button() == 1) and event.scenePos().x() < -25:
#This is also part of musicstructures.py createInitialSignatureArea() (Track). We set a margin against misclicks.
self.parentView.mainWindow.ui.actionData_Editor.trigger()
super().mouseReleaseEvent(event) super().mouseReleaseEvent(event)
def reactToHorizontalScroll(self, value:int): def reactToHorizontalScroll(self, value:int):

10
qtgui/submenus.py

@ -234,6 +234,8 @@ class SecondarySplitMenu(Submenu):
class SecondaryKeySignatureMenu(Submenu): class SecondaryKeySignatureMenu(Submenu):
def __init__(self, mainWindow): def __init__(self, mainWindow):
"""Init is only called once per program, during startup"""
super().__init__(mainWindow, translate("submenus", "root note is the cursor position"), hasOkCancelButtons=2) super().__init__(mainWindow, translate("submenus", "root note is the cursor position"), hasOkCancelButtons=2)
l = [("[{}] {}".format(num+1, modeString.title()), lambda r, modeString=modeString: api.insertCursorCommonKeySignature(modeString)) for num, modeString in enumerate(api.commonKeySignaturesAsList())] l = [("[{}] {}".format(num+1, modeString.title()), lambda r, modeString=modeString: api.insertCursorCommonKeySignature(modeString)) for num, modeString in enumerate(api.commonKeySignaturesAsList())]
@ -249,7 +251,6 @@ class SecondaryKeySignatureMenu(Submenu):
super().__call__() super().__call__()
class SecondaryDynamicsMenu(Submenu): class SecondaryDynamicsMenu(Submenu):
def __init__(self, mainWindow): def __init__(self, mainWindow):
super().__init__(mainWindow, translate("submenus", "choose a dynamic"), hasOkCancelButtons=2) super().__init__(mainWindow, translate("submenus", "choose a dynamic"), hasOkCancelButtons=2)
@ -269,10 +270,13 @@ class SecondaryDynamicsMenu(Submenu):
button.clicked.connect(self.done) button.clicked.connect(self.done)
class SecondaryMetricalInstructionMenu(Submenu): class SecondaryMetricalInstructionMenu(Submenu):
def __init__(self, mainWindow): def __init__(self, mainWindow, setInitialInsteadCursorInsert:bool=False):
super().__init__(mainWindow, translate("submenus", "choose a metrical instruction"), hasOkCancelButtons=2) super().__init__(mainWindow, translate("submenus", "choose a metrical instruction"), hasOkCancelButtons=2)
l = [("[{}] {}".format(num+1, modeString), lambda r, modeString=modeString: api.insertCommonMetricalInstrucions(modeString)) for num, modeString in enumerate(api.commonMetricalInstructionsAsList())] if setInitialInsteadCursorInsert: #different api function
l = [("[{}] {}".format(num+1, modeString), lambda r, modeString=modeString: api.insertCommonMetricalInstrucions(modeString, setInitialInsteadCursorInsert=True)) for num, modeString in enumerate(api.commonMetricalInstructionsAsList())]
else:
l = [("[{}] {}".format(num+1, modeString), lambda r, modeString=modeString: api.insertCommonMetricalInstrucions(modeString)) for num, modeString in enumerate(api.commonMetricalInstructionsAsList())]
for number, (prettyname, function) in enumerate(l): for number, (prettyname, function) in enumerate(l):
button = QtWidgets.QPushButton(prettyname) button = QtWidgets.QPushButton(prettyname)

35
qtgui/trackEditor.py

@ -22,6 +22,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging; logger = logging.getLogger(__name__); logger.info("import") import logging; logger = logging.getLogger(__name__); logger.info("import")
#Standard Library #Standard Library
from contextlib import contextmanager
#Third party #Third party
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
@ -33,8 +34,9 @@ from template.helper import pairwise
#Our own files #Our own files
from .constantsAndConfigs import constantsAndConfigs from .constantsAndConfigs import constantsAndConfigs
from .designer.trackWidget import Ui_trackGroupWidget from .designer.trackWidget import Ui_trackGroupWidget
from .submenus import TickWidget, CombinedTickWidget from .submenus import TickWidget, CombinedTickWidget, SecondaryMetricalInstructionMenu
from contextlib import contextmanager from .customkeysignature import CustomKeySignatureWidget
from .custommetricalinstruction import CustomMetricalInstructionWidget
import engine.api as api import engine.api as api
LIST_OF_CLEF_KEYWORDS = api.getPossibleClefKeywords() #TODO: translate? But keep the keyword as index. setData LIST_OF_CLEF_KEYWORDS = api.getPossibleClefKeywords() #TODO: translate? But keep the keyword as index. setData
@ -69,12 +71,9 @@ class TrackWidget(QtWidgets.QGroupBox):
self.ui.instrumentName.editingFinished.connect(self.nameChanged) self.ui.instrumentName.editingFinished.connect(self.nameChanged)
self.ui.shortInstrumentName.editingFinished.connect(self.nameChanged) self.ui.shortInstrumentName.editingFinished.connect(self.nameChanged)
#Set the initial clef. Unlike the metrical instruction the engine actually has a default clef, which is the treble clef
self.ui.initialClef_comboBox.addItems(LIST_OF_CLEF_KEYWORDS) #Current index is set in self.updateData self.ui.initialClef_comboBox.addItems(LIST_OF_CLEF_KEYWORDS) #Current index is set in self.updateData
self.ui.initialClef_comboBox.currentIndexChanged.connect(self.dataChanged) 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 #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 #However, we will not use normal checkable Menu actions since they close the menu after triggering. even blockSignals does not prevent closing
self.ccChannels = {} self.ccChannels = {}
@ -354,6 +353,18 @@ class TrackEditor(QtWidgets.QWidget):
allUpbeatsLayout.addWidget(self.allUpbeatsCombinedTickWidget) allUpbeatsLayout.addWidget(self.allUpbeatsCombinedTickWidget)
allUpbeatsLayout.addWidget(allUpbeatsPushButton) 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 #Reset all Advanced Views
foldAllAdvanvced = QtWidgets.QPushButton(translate("trackEditorPythonFile", "Fold all Advanced")) foldAllAdvanvced = QtWidgets.QPushButton(translate("trackEditorPythonFile", "Fold all Advanced"))
foldAllAdvanvced.clicked.connect(self.foldAllAdvanvced) foldAllAdvanvced.clicked.connect(self.foldAllAdvanvced)
@ -441,14 +452,14 @@ class TrackEditor(QtWidgets.QWidget):
for trackWidget in self.tracks.values(): for trackWidget in self.tracks.values():
trackWidget.ui.advanced.setChecked(True) trackWidget.ui.advanced.setChecked(True)
def setAllCustomKeySig(self):
CustomKeySignatureWidget(self.mainWindow, setInitialKeysigInsteadCursorInsert=True)
def setAllMetricalInstruction(self):
SecondaryMetricalInstructionMenu(self.mainWindow, setInitialInsteadCursorInsert=True).exec()
def keyPressEvent(self, event):
"""Escape closes the track editor"""
k = event.key() #49=1, 50=2 etc.
if k == QtCore.Qt.Key_Escape:
self.mainWindow.ui.actionData_Editor.setChecked(False)
self.mainWindow.toggleMainView()
super().keyPressEvent(event)
""" """
api.callbacks.updateBlockTrack.append(lambda trId, blocksExportData: self.tracks[trId].blockScene.regenerateBlocks(blocksExportData)) api.callbacks.updateBlockTrack.append(lambda trId, blocksExportData: self.tracks[trId].blockScene.regenerateBlocks(blocksExportData))

2
template/qtgui/mainwindow.py

@ -377,7 +377,7 @@ class MainWindow(QtWidgets.QMainWindow):
This event filter somehow does not differentiate between numpad This event filter somehow does not differentiate between numpad
on or numpad off. There maybe is another qt check for that. on or numpad off. There maybe is another qt check for that.
""" """
if (not self.menu.ui.menubar.activeAction()) and event.type() == 51 and type(event) is QtGui.QKeyEvent and event.modifiers() == QtCore.Qt.KeypadModifier and event.text() and event.text() in "0123456789": if (not self.menu.ui.menubar.activeAction()) and event.type() == QtCore.QEvent.ShortcutOverride and type(event) is QtGui.QKeyEvent and event.modifiers() == QtCore.Qt.KeypadModifier and event.text() and event.text() in "0123456789":
action = self.menu.hoverShortcutDict[event.text()] action = self.menu.hoverShortcutDict[event.text()]
if action: if action:
action.trigger() action.trigger()

Loading…
Cancel
Save