Compare commits

...

6 Commits

  1. 11
      CHANGELOG
  2. 3
      engine/api.py
  3. 19
      engine/items.py
  4. 89
      engine/lilypond.py
  5. 16
      engine/main.py
  6. 6
      engine/resources/lilypondTemplates/default.ly
  7. 3
      engine/track.py
  8. 74
      qtgui/designer/customKeySignature.py
  9. 72
      qtgui/designer/customKeySignature.ui
  10. 6
      qtgui/submenus.py

11
CHANGELOG

@ -5,6 +5,17 @@ Two empty lines before the next entry.
External contributors notice at the end of the line: (LastName, FirstName / nick)
## 2022-10-15 2.2.0
Add more time signatures to the quick-insert dialog: 8/4, three variants of 7/8 and two variants of 5/4 and more. Also reorder and better labels.
Fix splitting of notes that were created by a previous split.
Various small fixes, like typos in variable names and wrong string quotes. Small things can crash as well.
Lilypond:
Add transposition of the whole score to properties and metadata dialog
Only set tempo markings in parenthesis when user provided a string like Allegro
Add lilypond template filename to properties and metadata dialog. Will be symlinked into the session dir automatically.
Overhaul Key Signature GUI dialog to indicate "Deviation" better, instead of absolute accidentals.
## 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

3
engine/api.py

@ -1907,7 +1907,6 @@ def insertCommonKeySignature(root:int, scheme:str):
"Mixolydian": [0,0,0,0,0,0,-10],
"Locrian": [0,-10,-10,0,-10,-10,-10],
"Hollywood": [0,0,0,0,0,-10,-10], #The "Hollywood"-Scale. Stargate, Lord of the Rings etc.
#TODO: MOAR!
}
#Lilypond knows some standard key signature modes, which produce cleaner output than our default verbose note-for-note one.
@ -1915,7 +1914,7 @@ def insertCommonKeySignature(root:int, scheme:str):
if scheme.lower() in lySupported:
lyRootNote = pitchmath.pitch2ly[root].strip("'").strip(",").lower()
lyovrd = f"\\key {lyRootNote} \\{scheme.lower()}"
lyovrd = f"\n\\key {lyRootNote} \\{scheme.lower()}\n"
else:
lyovrd = None

19
engine/items.py

@ -2276,27 +2276,20 @@ class KeySignature(Item):
elif alteration == -20:
return "(" + str(step) + " . ," + "DOUBLE-FLAT)"
elif self.lilypondParameters["explicit"] and alteration == 0:
return "(" + str(step) + " . ," + "NATURAL)"
return "(" + str(step) + " . ," + "NATURAL)" #explicit is a legacy flag. It is not available to the user. We now (v2.2.0) always export everything explictely to get ly-transposition working
else:
return ""
return "(" + str(step) + " . ," + "NATURAL)"
#G Major ((3, 10), (0, 0), (4, 0), (1, 0), (5, 0), (2, 0), (6, 0))
#`((0 . ,NATURAL) (1 . ,NATURAL) (2 . ,NATURAL) (3 . ,SHARP) (4 . ,NATURAL) (5 . ,NATURAL) (6 . ,NATURAL))
schemepairs = " ".join(build(x[0], x[1]) for x in self.keysigList)
schemepairs = " ".join(schemepairs.split()) # split and join again to reduce all whitespaces to single ones.
subtextRoot = "\\once \\override Staff.KeySignature #'stencil = #(lambda (grob) (ly:stencil-combine-at-edge (ly:key-signature-interface::print grob) Y DOWN (grob-interpret-markup grob (markup #:small \"" + pitchmath.pitch2ly[self.root].strip(",").title() + "\")) 3)) " #3 is the space between keysig and text
lyKeysignature = subtextRoot + "\\set Staff.keyAlterations = #`( {} )".format(schemepairs)
assert schemepairs
#For Transposition in lilypond we need to explicitly export all scheme pairs, not only the one that differ from major.
#TODO: Root note text was removed with commit 7c76afa600cb7a4fca6800401d8e152e189112cd because they cannot get transposed. Maybe in the future…
lyKeysignature = f"\n\\key {pitchmath.pitch2ly[self.root].strip(',')} #`( {schemepairs} )\n"
return lyKeysignature
#Alternative
schemepairs = " ".join(build(num, alt) for num, alt in enumerate(self.deviationFromMajorScale))
schemepairs = " ".join(schemepairs.split()) # split and join again to reduce all whitespaces to single ones.
subtextRoot = "\\once \\override Staff.KeySignature #'stencil = #(lambda (grob) (ly:stencil-combine-at-edge (ly:key-signature-interface::print grob) Y DOWN (grob-interpret-markup grob (markup #:small \"" + pitchmath.pitch2ly[self.root].strip(",").title() + "\")) 3)) " #3 is the space between keysig and text
if schemepairs:
lyKeysignature = subtextRoot + f"\\key {pitchmath.pitch2ly[self.root].strip(',')} #`( {schemepairs} )"
else: #no explicit accidental
lyKeysignature = subtextRoot + "\\set Staff.keyAlterations = #`(( 0 . ,NATURAL))" #TODO: subtextRoot position is broken
return lyKeysignature
class Clef(Item):
"""A clef has no direct musical logical meaning. But it gives some hints:

89
engine/lilypond.py

@ -87,17 +87,44 @@ def lilyfy(string):
string = string.replace('"', '\\"')
return string
def fromTemplate(session, templateFile, data, meta, tempoStaff):
def fromTemplate(session, data, meta, tempoStaff):
"""Returns a string built from already ly-exported track data and a lilypond template.
Called by score.lilypond(), which is called by session.lilypond().
A template is either one of the built-ins "foo.ly" or
if the templateFile is None/"" the current filename+ly is used: "bar.lbj.ly"
Laborejo Markers in the template have the syntax %$$DATE$$ . That is pretty unique and additionaly a lilypond
comment.
meta is the actual score.metaData dictionary, not a copy. We use this to modify the template
path entry.
Laborejo Markers in the template have the syntax %$$DATE$$ .
That is pretty unique and additionally a lilypondcomment.
The template file is in metaData["template-file"]. The default is empty string,
which means we use the programs "default.ly" template.
For session management reasons this the user template must be a file in the session dir.
However, a system wide user library of lilypond export templates is very useful and convenient.
Thus we allow absolute paths as well. Or even relative and ~/ if the user wants. They will be
converted automatically to a symlink into the session dir and the absolute path will be
auto-replaced by the local filename to the symlink. (The absolute path will remain in metadata
and gui field until the file is found. Thus a typo will not lead to a broken symlink
creation)
If template file lookup fails for any reason we use the internal default.ly and log an error
message.
"""
#Load the template
templatePath = findTemplate(session, templateFile)
#Find and load the template
templateFile = meta["template-file"]
try:
templatePath = findTemplate(session, meta, templateFile)
except FileNotFoundError: #Fall back to default.py and try again
logger.error(f"User lilypond template {templateFile} not found. Will use the builtin default.ly instead.")
templatePath = findTemplate(session, meta, "") #If this fails something is indeed wrong and we let the error raise
except PermissionError: #Fall back to default.py and try again
logger.error(f"User lilypond template {templateFile} not readable. Will use the builtin default.ly instead.")
templatePath = findTemplate(session, meta, "") #If this fails something is indeed wrong and we let the error raise
#Now we know the templatePath exists and it is either default.ly or a file/link in our session dir.
with open(templatePath, 'r') as f:
templateString = f.read()
@ -109,27 +136,39 @@ def fromTemplate(session, templateFile, data, meta, tempoStaff):
templateString = templateString.replace("%$$VOICES$$", voicesString)
templateString = templateString.replace("%$$STRUCTURE$$", structureString)
templateString = templateString.replace("%$$TEMPOSTAFF$$", tempoStaff)
templateString = templateString.replace("%$$TRANSPOSITION$$", '\\transpose ' + " ".join(meta["transposition"].split()) ) #something like "c' f". Defaults to "c c ". Lilypond output without ""!
return templateString
def findTemplate(session, templateFile:str=None)->str:
"""returns a path. checks for existence.
There are two options:
- Use a standard template in SHARE/lilypondTemplates
- Use a template that belongs to the save file.
The one that belongs to the save file needs to be created by hand by the user.
If not we throw a FileNotFoundError"""
if templateFile:
path = os.path.join(PATHS["share"], "lilypondTemplates", templateFile)
else:
path = os.path.join(session.sessionPrefix, "template.laborejo2.ly") #the name is always the same because the sessionPrefix is the unqiue part
assert path.endswith(".ly")
if os.path.exists(path):
return path
else:
raise FileNotFoundError(templateFile)
def findTemplate(session, meta, templateFile:str)->str:
"""returns a path. checks for existence and read access through NSMclient functionality.
This will create a symlink in our session dir and always use this. It will also destructively
change the user metadata to this symlink file, if present."""
if not templateFile:
path = os.path.join(PATHS["share"], "lilypondTemplates", "default.ly")
return path #we don't need the checks below for our own file
#User provided a template file name or path
#Our NSM client can safely import any file. If we try to "import" a file already in our session dir nothing bad will happen, we just use the file.
assert session.nsmClient.importResource
logger.info("Trying to use user provided ly template: " + templateFile)
#This can throw FileNotFoundError or PermissionError, which we catch in the parent function. No need to check for ourselves again here.
#We get just the filename for symlinks in our session dir. Quickly check that and adjust to absolute path.
templateFile = os.path.expanduser(templateFile) # ~home
if os.path.basename(templateFile) == templateFile:
templateFile = os.path.join(session.sessionPrefix, templateFile)
#no need to check. even if that fails it will be caught safely below and reported to the log
path = session.nsmClient.importResource(templateFile) #it will always be an absolute path. Check the nsm INFO log.
meta["template-file"] = os.path.basename(path)
logger.info("Imported or re-used: " + path)
return path
def processData(data):
"""returns two strings. the first actual music data, VOICES, the second the structure and order,

16
engine/main.py

@ -46,7 +46,9 @@ class Data(template.engine.sequencer.Score):
#Lilypond Properties that might be used elsewhere as well, e.g. JACK Pretty Names
self.metaData = {key:"" for key in ("title", "subtitle", "dedication","composer","subsubtitle","instrument","meter","arranger", "poet","piece","opus","copyright","tagline", "subtext")}
self.metaData["metronome"] = True #show metronome in printout?
self.metaData["metronome"] = True #show metronome in printout? v2.1.0
self.metaData["transposition"] = "c c" # Whole score transposition for Lilypond. v2.2.0
self.metaData["template-file"] = "" # Lilypond Template file. If empty use default.ly . v2.2.0
self.currentMetronomeTrack = self.tracks[0] #A Laborejo Track, indepedent of currentTrack. The metronome is in self.metronome, set by the template Score.
self._processAfterInit()
@ -1058,7 +1060,9 @@ class Data(template.engine.sequencer.Score):
"""
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)
#the template file name is in metadata, but it can be empty or not existient
#From Template has direct access to the score metadata and WILL destructively modify it's own parameters, like the lilypond template entry.
return fromTemplate(session = self.parentSession, data = data, meta = self.metaData, tempoStaff = tempoStaff)
def getMidiInputNameAndUuid(self):
"""
@ -1093,6 +1097,14 @@ class Data(template.engine.sequencer.Score):
self.hiddenTracks = {Track.instanceFromSerializedData(parentData=self, serializedData=track):originalIndex for track, originalIndex in serializedData["hiddenTracks"]}
self.tempoTrack = TempoTrack.instanceFromSerializedData(serializedData["tempoTrack"], parentData = self)
self.metaData = serializedData["metaData"]
if not "metronome" in self.metaData: #2.1.0
self.metaData["metronome"] = True #replicate __init__ default
if not "transposition" in self.metaData: #2.2.0
self.metaData["transposition"] = "c c" #replicate __init__ default
if not "template-file" in self.metaData: #2.2.0
self.metaData["template-file"] = "" #replicate __init__ default
self.currentMetronomeTrack = self.tracks[serializedData["currentMetronomeTrackIndex"]]
self._processAfterInit()
return self

6
engine/resources/lilypondTemplates/default.ly

@ -95,7 +95,10 @@ tempoStaff = { %$$TEMPOSTAFF$$ }
%$$VOICES$$
\score{
\score {
%Transpose the entire score. Nothing happens when "c c"
%$$TRANSPOSITION$$
<<
%How the definitions are arranged. Only staffgroups (staff prefix) and staffs, merged as voices.
@ -103,6 +106,7 @@ tempoStaff = { %$$TEMPOSTAFF$$ }
%$$STRUCTURE$$
>>}
\markup{
%\column {
%$$SUBTEXT$$

3
engine/track.py

@ -865,8 +865,7 @@ class Track(object):
keySignature = ""
break
else:
keySignature = self.initialKeySignature.lilypond(carryLilypondRanges) + "\n"
keySignature = self.initialKeySignature.lilypond(carryLilypondRanges)
upbeatLy = "\\partial {} ".format(Duration.createByGuessing(self.upbeatInTicks).lilypond(carryLilypondRanges)) if self.upbeatInTicks else ""

74
qtgui/designer/customKeySignature.py

@ -2,7 +2,7 @@
# Form implementation generated from reading ui file 'customKeySignature.ui'
#
# Created by: PyQt5 UI code generator 5.15.6
# Created by: PyQt5 UI code generator 5.15.7
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
@ -322,7 +322,7 @@ class Ui_customKeySignature(object):
def retranslateUi(self, customKeySignature):
_translate = QtCore.QCoreApplication.translate
customKeySignature.setWindowTitle(_translate("customKeySignature", "Custom Key Signature"))
self.keysig_instruction_label.setText(_translate("customKeySignature", "Design your own scale. The key signature will be calculated. Choose a root note and how each step deviates from it\'s major scale. E.g. leaving all switches on \"natural\" will result in the major scale. Setting step III to flat will result in \"melodic minor\"."))
self.keysig_instruction_label.setText(_translate("customKeySignature", "Design your own scale. The key signature will be calculated. Choose a root note and how each step deviates from it\'s major scale. E.g. leaving all switches on = will result in the major scale. Setting step III to will result in \"melodic minor\"."))
self.group_root.setTitle(_translate("customKeySignature", "Root Note"))
self.root_dis.setText(_translate("customKeySignature", "D♯"))
self.root_des.setText(_translate("customKeySignature", "D♭"))
@ -347,51 +347,51 @@ class Ui_customKeySignature(object):
self.root_bis.setText(_translate("customKeySignature", "B♯ / H♯"))
self.group_accidentals.setTitle(_translate("customKeySignature", "Scale - Deviation from the Major Scale"))
self.group_c.setTitle(_translate("customKeySignature", ""))
self.isis_c.setText(_translate("customKeySignature", "𝄪"))
self.is_c.setText(_translate("customKeySignature", ""))
self.nat_c.setText(_translate("customKeySignature", ""))
self.es_c.setText(_translate("customKeySignature", ""))
self.eses_c.setText(_translate("customKeySignature", "𝄫"))
self.isis_c.setText(_translate("customKeySignature", ""))
self.is_c.setText(_translate("customKeySignature", ""))
self.nat_c.setText(_translate("customKeySignature", "="))
self.es_c.setText(_translate("customKeySignature", ""))
self.eses_c.setText(_translate("customKeySignature", ""))
self.label_result_c.setText(_translate("customKeySignature", "c"))
self.groupd_d.setTitle(_translate("customKeySignature", ""))
self.isis_d.setText(_translate("customKeySignature", "𝄪"))
self.is_d.setText(_translate("customKeySignature", ""))
self.nat_d.setText(_translate("customKeySignature", ""))
self.es_d.setText(_translate("customKeySignature", ""))
self.eses_d.setText(_translate("customKeySignature", "𝄫"))
self.isis_d.setText(_translate("customKeySignature", ""))
self.is_d.setText(_translate("customKeySignature", ""))
self.nat_d.setText(_translate("customKeySignature", "="))
self.es_d.setText(_translate("customKeySignature", ""))
self.eses_d.setText(_translate("customKeySignature", ""))
self.label_result_d.setText(_translate("customKeySignature", "d"))
self.group_e.setTitle(_translate("customKeySignature", ""))
self.isis_e.setText(_translate("customKeySignature", "𝄪"))
self.is_e.setText(_translate("customKeySignature", ""))
self.nat_e.setText(_translate("customKeySignature", ""))
self.es_e.setText(_translate("customKeySignature", ""))
self.eses_e.setText(_translate("customKeySignature", "𝄫"))
self.isis_e.setText(_translate("customKeySignature", ""))
self.is_e.setText(_translate("customKeySignature", ""))
self.nat_e.setText(_translate("customKeySignature", "="))
self.es_e.setText(_translate("customKeySignature", ""))
self.eses_e.setText(_translate("customKeySignature", ""))
self.label_result_e.setText(_translate("customKeySignature", "e"))
self.group_f.setTitle(_translate("customKeySignature", ""))
self.isis_f.setText(_translate("customKeySignature", "𝄪"))
self.is_f.setText(_translate("customKeySignature", ""))
self.nat_f.setText(_translate("customKeySignature", ""))
self.es_f.setText(_translate("customKeySignature", ""))
self.eses_f.setText(_translate("customKeySignature", "𝄫"))
self.isis_f.setText(_translate("customKeySignature", ""))
self.is_f.setText(_translate("customKeySignature", ""))
self.nat_f.setText(_translate("customKeySignature", "="))
self.es_f.setText(_translate("customKeySignature", ""))
self.eses_f.setText(_translate("customKeySignature", ""))
self.label_result_f.setText(_translate("customKeySignature", "f"))
self.group_g.setTitle(_translate("customKeySignature", ""))
self.isis_g.setText(_translate("customKeySignature", "𝄪"))
self.is_g.setText(_translate("customKeySignature", ""))
self.nat_g.setText(_translate("customKeySignature", ""))
self.es_g.setText(_translate("customKeySignature", ""))
self.eses_g.setText(_translate("customKeySignature", "𝄫"))
self.isis_g.setText(_translate("customKeySignature", ""))
self.is_g.setText(_translate("customKeySignature", ""))
self.nat_g.setText(_translate("customKeySignature", "="))
self.es_g.setText(_translate("customKeySignature", ""))
self.eses_g.setText(_translate("customKeySignature", ""))
self.label_result_g.setText(_translate("customKeySignature", "g"))
self.group_a.setTitle(_translate("customKeySignature", ""))
self.isis_a.setText(_translate("customKeySignature", "𝄪"))
self.is_a.setText(_translate("customKeySignature", ""))
self.nat_a.setText(_translate("customKeySignature", ""))
self.es_a.setText(_translate("customKeySignature", ""))
self.eses_a.setText(_translate("customKeySignature", "𝄫"))
self.isis_a.setText(_translate("customKeySignature", ""))
self.is_a.setText(_translate("customKeySignature", ""))
self.nat_a.setText(_translate("customKeySignature", "="))
self.es_a.setText(_translate("customKeySignature", ""))
self.eses_a.setText(_translate("customKeySignature", ""))
self.label_result_a.setText(_translate("customKeySignature", "a"))
self.group_b.setTitle(_translate("customKeySignature", ""))
self.isis_b.setText(_translate("customKeySignature", "𝄪"))
self.is_b.setText(_translate("customKeySignature", ""))
self.nat_b.setText(_translate("customKeySignature", ""))
self.es_b.setText(_translate("customKeySignature", ""))
self.eses_b.setText(_translate("customKeySignature", "𝄫"))
self.isis_b.setText(_translate("customKeySignature", ""))
self.is_b.setText(_translate("customKeySignature", ""))
self.nat_b.setText(_translate("customKeySignature", "="))
self.es_b.setText(_translate("customKeySignature", ""))
self.eses_b.setText(_translate("customKeySignature", ""))
self.label_result_b.setText(_translate("customKeySignature", "b"))

72
qtgui/designer/customKeySignature.ui

@ -29,7 +29,7 @@
</sizepolicy>
</property>
<property name="text">
<string>Design your own scale. The key signature will be calculated. Choose a root note and how each step deviates from it's major scale. E.g. leaving all switches on &quot;natural&quot; will result in the major scale. Setting step III to flat will result in &quot;melodic minor&quot;.</string>
<string>Design your own scale. The key signature will be calculated. Choose a root note and how each step deviates from it's major scale. E.g. leaving all switches on = will result in the major scale. Setting step III to ↓ will result in &quot;melodic minor&quot;.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
@ -235,21 +235,21 @@
<item>
<widget class="QRadioButton" name="isis_c">
<property name="text">
<string>𝄪</string>
<string></string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="is_c">
<property name="text">
<string></string>
<string></string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="nat_c">
<property name="text">
<string></string>
<string>=</string>
</property>
<property name="checked">
<bool>true</bool>
@ -259,14 +259,14 @@
<item>
<widget class="QRadioButton" name="es_c">
<property name="text">
<string></string>
<string></string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="eses_c">
<property name="text">
<string>𝄫</string>
<string></string>
</property>
</widget>
</item>
@ -314,21 +314,21 @@
<item>
<widget class="QRadioButton" name="isis_d">
<property name="text">
<string>𝄪</string>
<string></string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="is_d">
<property name="text">
<string></string>
<string></string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="nat_d">
<property name="text">
<string></string>
<string>=</string>
</property>
<property name="checked">
<bool>true</bool>
@ -338,14 +338,14 @@
<item>
<widget class="QRadioButton" name="es_d">
<property name="text">
<string></string>
<string></string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="eses_d">
<property name="text">
<string>𝄫</string>
<string></string>
</property>
</widget>
</item>
@ -393,21 +393,21 @@
<item>
<widget class="QRadioButton" name="isis_e">
<property name="text">
<string>𝄪</string>
<string></string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="is_e">
<property name="text">
<string></string>
<string></string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="nat_e">
<property name="text">
<string></string>
<string>=</string>
</property>
<property name="checked">
<bool>true</bool>
@ -417,14 +417,14 @@
<item>
<widget class="QRadioButton" name="es_e">
<property name="text">
<string></string>
<string></string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="eses_e">
<property name="text">
<string>𝄫</string>
<string></string>
</property>
</widget>
</item>
@ -472,21 +472,21 @@
<item>
<widget class="QRadioButton" name="isis_f">
<property name="text">
<string>𝄪</string>
<string></string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="is_f">
<property name="text">
<string></string>
<string></string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="nat_f">
<property name="text">
<string></string>
<string>=</string>
</property>
<property name="checked">
<bool>true</bool>
@ -496,14 +496,14 @@
<item>
<widget class="QRadioButton" name="es_f">
<property name="text">
<string></string>
<string></string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="eses_f">
<property name="text">
<string>𝄫</string>
<string></string>
</property>
</widget>
</item>
@ -551,21 +551,21 @@
<item>
<widget class="QRadioButton" name="isis_g">
<property name="text">
<string>𝄪</string>
<string></string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="is_g">
<property name="text">
<string></string>
<string></string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="nat_g">
<property name="text">
<string></string>
<string>=</string>
</property>
<property name="checked">
<bool>true</bool>
@ -575,14 +575,14 @@
<item>
<widget class="QRadioButton" name="es_g">
<property name="text">
<string></string>
<string></string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="eses_g">
<property name="text">
<string>𝄫</string>
<string></string>
</property>
</widget>
</item>
@ -630,21 +630,21 @@
<item>
<widget class="QRadioButton" name="isis_a">
<property name="text">
<string>𝄪</string>
<string></string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="is_a">
<property name="text">
<string></string>
<string></string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="nat_a">
<property name="text">
<string></string>
<string>=</string>
</property>
<property name="checked">
<bool>true</bool>
@ -654,14 +654,14 @@
<item>
<widget class="QRadioButton" name="es_a">
<property name="text">
<string></string>
<string></string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="eses_a">
<property name="text">
<string>𝄫</string>
<string></string>
</property>
</widget>
</item>
@ -709,21 +709,21 @@
<item>
<widget class="QRadioButton" name="isis_b">
<property name="text">
<string>𝄪</string>
<string></string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="is_b">
<property name="text">
<string></string>
<string></string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="nat_b">
<property name="text">
<string></string>
<string>=</string>
</property>
<property name="checked">
<bool>true</bool>
@ -733,14 +733,14 @@
<item>
<widget class="QRadioButton" name="es_b">
<property name="text">
<string></string>
<string></string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="eses_b">
<property name="text">
<string>𝄫</string>
<string></string>
</property>
</widget>
</item>

6
qtgui/submenus.py

@ -474,11 +474,15 @@ class TransposeMenu(Submenu):
self.done(True)
class SecondaryProperties(Submenu):
"""Lilypodn Settings and Properties.
Directly edits the backend score meta data. There is no api and no callbacks"""
def __init__(self, mainWindow):
"""Directly edits the backend score meta data. There is no api and no callbacks"""
super().__init__(mainWindow, translate("submenus", "Lilypond Properties and Metada"), hasOkCancelButtons=True)
dictionary = api.getMetadata() #Do not confuse with template/config METADATA. This is Lilypond title, composer etc.
#The dictionary is mutable. We edit in place. #TODO: This might bite us in the future. But maybe it will not...
test = set(type(key) for key in dictionary.keys())
assert len(test) == 1

Loading…
Cancel
Save