Browse Source

add initial area for signatures

master
Nils 2 months 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
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)
When not in F4-StepInput Mode use midi keyboard as pitch-cursor.
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"""
newIndex = len(session.data.tracks)
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
return (id(newTrack))
@ -1851,6 +1857,20 @@ def insertKeySignature(root:int, scheme:list, lilypondOverride:str=None):
keysig.lilypondParameters["override"] = lilypondOverride
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():
"""For the GUIs convenience so they can populate their
drop down lists.
@ -1928,6 +1948,23 @@ def insertMetricalInstruction(treeOfInstructions, lilypondOverride = None):
item.lilypondParameters["override"] = lilypondOverride
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():
"""for musical reasons 5/4 and 7/8 and other a-symetrical
metrical instructions cannot be in here since it is unknown which
@ -1946,7 +1983,7 @@ def commonMetricalInstructionsAsList():
"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
they will not work when exported."""
schemes = {
@ -1973,7 +2010,11 @@ def insertCommonMetricalInstrucions(scheme):
"1/1" : "\\cadenzaOff \\time 1/1",
"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():
@ -2723,7 +2764,7 @@ def lilypondText(text:str):
try:
insertItem(items.LilypondText(text))
except Exception as err:
logger.error(err)
logger.error(f"Text Item Lilypond Error for '{text}': " + err)
def lilypondMark(text:str, aboveStaff=True):
"""This is actual human-readable text, called a Mark by lilypond.
@ -2747,16 +2788,18 @@ def lilypondMark(text:str, aboveStaff=True):
def exportLilypond(absoluteFilePath):
save()
lilypond.saveAsLilypond(session.data, absoluteFilePath)
#try:
# lilypond.saveAsLilypond(session.data, absoluteFilePath)
#except Exception as err:
# logger.error(err)
try:
lilypond.saveAsLilypond(session.data, absoluteFilePath)
except Exception as err:
logger.error(err)
def showPDF():
try:
save()
lilypond.saveAsLilypondPDF(session.data, openPDF = True)
except Exception as err:
logger.error("PDF/Lilypond Exception. The following is a PYTHON, not a lilypond error:")
print (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):
#absoluteFilePath = self.absoluteFilePath + ".ly" if (not absoluteFilePath) else absoluteFilePath
assert absoluteFilePath.endswith(".ly")
assert absoluteFilePath.endswith(".ly"), absoluteFilePath
result = score.lilypond()
if result:
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.
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
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().
This means init is always position 0."""
defaultClef = Clef("treble")
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):
self.track = track
@ -286,10 +283,9 @@ class TrackState(object):
self.ticksSinceLastMeasureStart = 0 #Only for export.
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:
self.keySignatures = [self.defaultKeySignature] #C Major
#self.clefs = [self.defaultClef]
self.keySignatures = [track.initialKeySignature]
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.dynamicRamp = None #Not for cursor movement so it is not a stack.
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.
#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.initialMetricalInstruction = MetricalInstruction(tuple(), isMetrical = False)
#self.initialKeySignature = KeySignature(20, [0,0,0,0,0,0,0])
self.initialKeySignature = KeySignature(20, [0,0,0,0,0,0,0]) #C Major
self.initialMetricalInstruction = MetricalInstruction(tuple(), isMetrical = False)
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.
"""
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]:
if type(item) is MetricalInstruction:
if type(item) is MetricalInstruction: #don't use the initial. use the user provided one.
timeSig = ""
break
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]:
if type(item) is Clef:
if type(item) is Clef: #don't use the initial. use the user provided one.
clef = ""
break
else:
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 ""
@ -911,7 +921,7 @@ class Track(object):
pass
if lyData:
return clef + timeSig + upbeatLy + lyData + "\n"
return clef + keySignature + timeSig + upbeatLy + lyData + "\n"
else:
return "" #Empty track
@ -926,6 +936,8 @@ class Track(object):
"double" : self.double,
"initialClefKeyword" : self.initialClefKeyword,
"initialKeySignature" : self.initialKeySignature.serialize(),
"initialMetricalInstruction" : self.initialMetricalInstruction.serialize(),
"initialMidiChannel" : self.initialMidiChannel,
"initialMidiProgram" : self.initialMidiProgram,
"initialMidiBankMsb" : self.initialMidiBankMsb,
@ -947,8 +959,8 @@ class Track(object):
self.upbeatInTicks = int(serializedData["upbeatInTicks"])
self.blocks = [Block.instanceFromSerializedData(block, parentObject = self) for block in serializedData["blocks"]]
self.durationSettingsSignature = DurationSettingsSignature.instanceFromSerializedData(serializedData["durationSettingsSignature"], parentObject = self)
self.dynamicSettingsSignature = DynamicSettingsSignature.instanceFromSerializedData(serializedData["dynamicSettingsSignature"], parentObject = self)
self.durationSettingsSignature = DurationSettingsSignature.instanceFromSerializedData(serializedData["durationSettingsSignature"], parentObject=self)
self.dynamicSettingsSignature = DynamicSettingsSignature.instanceFromSerializedData(serializedData["dynamicSettingsSignature"], parentObject=self)
self.double = serializedData["double"]
self.initialMidiChannel = serializedData["initialMidiChannel"]
self.initialMidiProgram = serializedData["initialMidiProgram"]
@ -966,6 +978,16 @@ class Track(object):
else:
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()
return self
@ -1254,7 +1276,10 @@ class Track(object):
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["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
t = (midiNotesBinaryCboxData, 0, self.state.tickindex)

17
qtgui/customkeysignature.py

@ -34,13 +34,21 @@ import engine.api as api
class CustomKeySignatureWidget(QtWidgets.QDialog):
def __init__(self, mainWindow):
"""Init is called everytime the dialog gets shown"""
def __init__(self, mainWindow, setInitialKeysigInsteadCursorInsert:bool=False):
"""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)
self.mainWindow = mainWindow
self.ui = Ui_customKeySignature()
self.ui.setupUi(self)
self.setInitialKeysigInsteadCursorInsert = setInitialKeysigInsteadCursorInsert
self.ui.buttonBox.accepted.connect(self.process)
self.ui.buttonBox.rejected.connect(self.reject)
@ -242,6 +250,9 @@ class CustomKeySignatureWidget(QtWidgets.QDialog):
if button.isChecked():
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)

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.
self.bigNatural = GuiKeySignature.accidentals[0]() #"big natural"... hö hö hö hö
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.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.
@ -765,7 +765,7 @@ class GuiTimeSignature(GuiItem):
class GuiMetricalInstruction(GuiItem):
def __init__(self, staticItem):
def __init__(self, staticItem, drawMarker=True):
super().__init__(staticItem)
if staticItem["oneMeasureInTicks"] == 0:
@ -790,9 +790,10 @@ class GuiMetricalInstruction(GuiItem):
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.marker = GuiPositionMarker()
self.marker.setParentItem(self)
self.marker.setPos(0,0)
if drawMarker:
self.marker = GuiPositionMarker()
self.marker.setParentItem(self)
self.marker.setPos(0,0)
def treeToPrettyString(self, tree):
"""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):
"""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():
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
from . import graphs
from .items import staticItem2Item, GuiTieCurveGraphicsItem, GuiClef
from .items import staticItem2Item, GuiTieCurveGraphicsItem, GuiClef, GuiKeySignature, GuiMetricalInstruction
from .constantsAndConfigs import constantsAndConfigs
from .submenus import BlockPropertiesEdit
@ -450,6 +450,34 @@ class GuiTrack(QtWidgets.QGraphicsItem):
super().__init__()
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):
"""Create staff objects including simple barlines"""
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.setParentItem(self)
metaDataDict = staticRepresentationList.pop()
#Initial Signatures, if present. Negative X-axis values
clefSVGItem = GuiClef.clefs[metaDataDict["initialClef"]]() #just the graphics
self.anchor.addToGroup(clefSVGItem)
clefSVGItem.setPos(sceneXOffsetForInitialSigs+5, GuiClef.cleffYOnStaff[metaDataDict["initialClef"]])
metaDataDict = staticRepresentationList.pop() #we want the dict, but we also want it separated from the item list. It is, by specs, the last item.
self.createInitialSignatureArea(metaDataDict)
#Real items, positive X-axis values
for staticItem in staticRepresentationList:

8
qtgui/scorescene.py

@ -336,9 +336,6 @@ class GuiScore(QtWidgets.QGraphicsScene):
self.updateSceneRect()
#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):
@ -601,6 +598,11 @@ class GuiScore(QtWidgets.QGraphicsScene):
else:
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)
def reactToHorizontalScroll(self, value:int):

10
qtgui/submenus.py

@ -234,6 +234,8 @@ class SecondarySplitMenu(Submenu):
class SecondaryKeySignatureMenu(Submenu):
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)
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__()
class SecondaryDynamicsMenu(Submenu):
def __init__(self, mainWindow):
super().__init__(mainWindow, translate("submenus", "choose a dynamic"), hasOkCancelButtons=2)
@ -269,10 +270,13 @@ class SecondaryDynamicsMenu(Submenu):
button.clicked.connect(self.done)
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)
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):
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")
#Standard Library
from contextlib import contextmanager
#Third party
from PyQt5 import QtCore, QtGui, QtWidgets
@ -33,8 +34,9 @@ from template.helper import pairwise
#Our own files
from .constantsAndConfigs import constantsAndConfigs
from .designer.trackWidget import Ui_trackGroupWidget
from .submenus import TickWidget, CombinedTickWidget
from contextlib import contextmanager
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
@ -69,12 +71,9 @@ class TrackWidget(QtWidgets.QGroupBox):
self.ui.instrumentName.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.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 = {}
@ -354,6 +353,18 @@ class TrackEditor(QtWidgets.QWidget):
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)
@ -441,14 +452,14 @@ class TrackEditor(QtWidgets.QWidget):
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()
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))

2
template/qtgui/mainwindow.py

@ -377,7 +377,7 @@ class MainWindow(QtWidgets.QMainWindow):
This event filter somehow does not differentiate between numpad
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()]
if action:
action.trigger()

Loading…
Cancel
Save