Browse Source

add unused menu for custom keysigs. WIP

master
Nils 3 months ago
parent
commit
94b5b78f41
  1. 2
      qtgui/customkeysignature.py
  2. 287
      qtgui/custommetricalinstruction.py
  3. 15
      qtgui/menu.py

2
qtgui/customkeysignature.py

@ -196,8 +196,6 @@ class CustomKeySignatureWidget(QtWidgets.QDialog):
mod = 10*counter-20 #offset by -20 because the scale starts at -20 for double flat but we enumerate from 0
#keysig = api.majorKeySignature(selectedRootPitch) #we deal with deviation from the major scale so we need the major scale for that root note
plainPitch = plain(stepInMajorRootScale + mod)
pitchOk = diatonicIndex(stepInMajorRootScale) == diatonicIndex(plainPitch) #did we cross a diatonic note boundary? That means this was a triple accidental

287
qtgui/custommetricalinstruction.py

@ -0,0 +1,287 @@
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Copyright 2022, Nils Hilbricht, Germany ( https://www.hilbricht.net )
This file is part of the Laborejo Software Suite ( https://www.laborejo.org ),
Laborejo2 is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
import logging; logger = logging.getLogger(__name__); logger.info("import")
#Third Party
from PyQt5 import QtCore, QtGui, QtWidgets
translate = QtCore.QCoreApplication.translate
#Template Modules
#Our Modules
from .submenus import CombinedTickWidget
import engine.api as api
def nothing(*args):
pass
class CustomMetricalInstructionWidget(QtWidgets.QDialog):
def __init__(self, mainWindow):
"""Init is called everytime the dialog gets shown"""
super().__init__(mainWindow)
self.mainWindow = mainWindow
#Add the title as simple LabelWidget
self.mainLayout = QtWidgets.QVBoxLayout(self)
self.label = QtWidgets.QLabel(translate("CustomMetricalInstructionWidget", "Design your own Metrical Instruction. These are not just time signatures, please refer to the manual!"))
self.label.setWordWrap(True)
self.mainLayout.addWidget(self.label)
#All other Widgets are in a Formlayout
self.subFormLayout = QtWidgets.QFormLayout()
self.mainLayout.addLayout(self.subFormLayout)
#Lilypond Override, must be specified
self.lilypondOverride = QtWidgets.QLineEdit("\\time 4/4")
self.subFormLayout.addRow(translate("CustomMetricalInstructionWidget", "Simplified Lilypond"), self.lilypondOverride)
#Chain Measures Widget
#We use this to determine the number of measure-designer-widgets shown
self.chainMeasuresSpinBox = QtWidgets.QSpinBox()
self.chainMeasuresSpinBox.setMinimum(1)
self.chainMeasuresSpinBox.setMaximum(8) #arbitrary, but far beyond musical necessity
self.subFormLayout.addRow(translate("CustomMetricalInstructionWidget", "Chain how many measures?"), self.chainMeasuresSpinBox)
#Is Metrical?
#If False all dynamic playback modifications based on the metre will be deactivated but barlines will still be created.
self.isMetricalCheckBox = QtWidgets.QCheckBox()
self.isMetricalCheckBox.setChecked(True)
self.subFormLayout.addRow(translate("CustomMetricalInstructionWidget", "Rendered Meter?"), self.isMetricalCheckBox)
#We construct all sub widgets now and hide them instead of creating and deleting them on-the-fly
#This way they keep their values if temporarily removed through user input by reducing self.chainMeasuresSpinBox.value
self.measureWidgets = []
for i in range(self.chainMeasuresSpinBox.maximum()):
mw = MeasureWidget(self)
self.measureWidgets.append(mw)
self.mainLayout.addWidget(mw)
mw.hide()
#Set Values, trigger callbacks
self.chainMeasuresSpinBox.valueChanged.connect(self.adjustNumberOfMeasureWidgets)
self.chainMeasuresSpinBox.setValue(1)
self.adjustNumberOfMeasureWidgets(self.chainMeasuresSpinBox.value())
#Finally add ButtonBox to the main layout, not to the formlayout
self.buttonBox = QtWidgets.QDialogButtonBox()
self.okButton = self.buttonBox.addButton(QtWidgets.QDialogButtonBox.Ok)
self.cancelButton = self.buttonBox.addButton(QtWidgets.QDialogButtonBox.Cancel)
self.buttonBox.accepted.connect(self.process)
self.buttonBox.rejected.connect(self.reject)
self.mainLayout.addWidget(self.buttonBox)
self.exec()
def showEvent(self, event):
super().showEvent(event)
self.adjustSize()
def adjustNumberOfMeasureWidgets(self, newValue:int):
"""Just hide and show. The maximum is fixed."""
for mw in self.measureWidgets:
mw.hide()
for mw in self.measureWidgets[:newValue]:
mw.show()
self.adjustSize()
def sanityCheck(self):
"""Called by child measure widgets after any change.
Do not call mw.convert in here, this will be recursive!"""
self.okButton.setEnabled(True)
for mw in self.measureWidgets:
if not mw.isVisible():
break
if not mw.validState:
self.okButton.setEnabled(False)
def process(self):
"""Pressed ok"""
result = []
for mw in self.measureWidgets:
# All visible mw are + Compound Measure "Half Barline"
# Most common case is just one mw visible.
if not mw.isVisible():
break
con = mw.convert()
result.append(con)
#api.insertMetricalInstruction
self.done(True)
class MeasureWidget(QtWidgets.QGroupBox):
"""Sub Widget for the user to choose strong and weak positions"""
def __init__(self, parentWidget):
super().__init__(parentWidget)
self.maxLevel = 3
self.parentWidget = parentWidget
self.layout = QtWidgets.QVBoxLayout(self)
self.validState = True
#Container
self.subFormLayout = QtWidgets.QFormLayout()
self.layout.addLayout(self.subFormLayout)
#Positions Widget
self.howManyPositionsSpinBox = QtWidgets.QSpinBox()
self.howManyPositionsSpinBox.setMinimum(1)
self.howManyPositionsSpinBox.setMaximum(64) #arbitrary, but far beyond musical necessity
self.subFormLayout.addRow(translate("CustomMetricalInstructionWidget", "How many positions"), self.howManyPositionsSpinBox)
#Base Duration Widget. Must not be 0
self.baseDuration = CombinedTickWidget(parentWidget.mainWindow)
self.subFormLayout.addRow(translate("CustomMetricalInstructionWidget", "Base duration in ticks"), self.baseDuration)
#The position sliders
self.positionLayout = QtWidgets.QHBoxLayout()
self.layout.addLayout(self.positionLayout)
self.positionLayout.addStretch()
self.positions = []
for i in range(self.howManyPositionsSpinBox.maximum()):
sliderCombo = QtWidgets.QVBoxLayout()
self.positionLayout.addLayout(sliderCombo)
#Show position counter above
counterLabel = QtWidgets.QLabel(f"{i+1}")
counterLabel.setAlignment(QtCore.Qt.AlignCenter)
sliderCombo.addWidget(counterLabel)
counterLabel.hide()
#Vertical Slider in the Middle
line = QtWidgets.QSlider()
line.setMinimum(0)
if i==0:
line.setMaximum(self.maxLevel+1)
line.setValue(self.maxLevel+1)
line.setEnabled(False)
else:
line.setMaximum(self.maxLevel)
line.setPageStep(1)
line.setSingleStep(1)
line.setOrientation(QtCore.Qt.Vertical)
line.setTickPosition(QtWidgets.QSlider.TicksAbove)
line.setTickInterval(1)
sliderCombo.addWidget(line)
line.hide()
#Stress-Level Below
numberLabel = QtWidgets.QLabel("0")
numberLabel.setAlignment(QtCore.Qt.AlignCenter)
sliderCombo.addWidget(numberLabel)
numberLabel.hide()
line.valueChanged.connect(lambda v,l=numberLabel: l.setText(str(v)))
line.valueChanged.connect(self.convert)
self.positions.append((line, numberLabel, counterLabel))
self.positionLayout.addStretch()
#Signals and Init Values
self.howManyPositionsSpinBox.valueChanged.connect(self.setNumberOfPositions)
self.howManyPositionsSpinBox.setValue(4)
self.baseDuration.setValue(api.D4)
def convert(self):
"""Convert our linear representation into engine tree lists/tuplets.
There are some rules that make the task easier:
* The starting number is always one higher than the max user-choice. It exists only once.
* All groups containing zeroes are on the same level. This is not allowed: ((400) 20),
it must be ((400) (20))
"""
levels = set(line.value() for line, numberLabel, counterLabel in self.positions if line.isVisible())
levels.remove(self.maxLevel + 1)
result = ""
if not 0 in levels:
self.validState = False
levels = () #incorrect
else:
self.validState = True
highest = max(levels)
openGroupsStack = { l:False for l in levels } #True if a group is currently open
#for i in range(len(levels)):
result = []
ticks = self.baseDuration.value()
lastNonZeroValue = -1
for line, numberLabel, counterLabel in self.positions:
if not line.isVisible():
break
v = line.value()
if openGroupsStack[v]:
pass
else:
currentGroup = []
"""
if v == 0:
result += f"{ticks}, "
elif lastNonZeroValue < v:
result += f"({ticks}, "
lastNonZeroValue = v
elif lastNonZeroValue > v:
result += f"{ticks}), "
lastNonZeroValue = v
"""
#print (result)
self.parentWidget.sanityCheck()
return levels
def setNumberOfPositions(self, newValue:int):
"""Called by parent signal. Just hide and show"""
for line, stressLabel, counterLabel in self.positions:
line.hide()
stressLabel.hide()
counterLabel.hide()
for line, stressLabel, counterLabel in self.positions[:newValue]:
line.show()
stressLabel.show()
counterLabel.show()
stressLabel.setText(str(line.value()))
self.positions[0][0].paintEvent = nothing #"Hide" (but keep spacing) the slider for the first measure position. It is fixed to maxLevel + 1

15
qtgui/menu.py

@ -39,6 +39,7 @@ from engine.midiinput.stepmidiinput import stepMidiInput #singleton instance
from .constantsAndConfigs import constantsAndConfigs
from .submenus import SecondaryClefMenu, SecondaryKeySignatureMenu, SecondaryDynamicsMenu, SecondaryMetricalInstructionMenu, SecondaryTempoChangeMenu, SecondaryTemporaryTempoChangeMenu, SecondarySplitMenu, TransposeMenu, pedalNoteChooser, SecondaryProperties, SecondaryProgramChangeMenu, SecondaryChannelChangeMenu, ChooseOne, forwardText, SecondaryMultimeasureRestMenu
from .customkeysignature import CustomKeySignatureWidget
from .custommetricalinstruction import CustomMetricalInstructionWidget
class ModalKeys(object):
def __init__(self):
@ -163,11 +164,13 @@ class MenuActionDatabase(object):
self.mainWindow.ui.actionUp : api.up,
self.mainWindow.ui.actionDown : api.down,
self.mainWindow.ui.actionSelectUp : api.selectUp, #aka "current chord select"
self.mainWindow.ui.actionSelectDown : api.selectDown, #aka "current chord select"
self.mainWindow.ui.actionUpOctave : api.upOctave,
self.mainWindow.ui.actionDownOctave : api.downOctave,
self.mainWindow.ui.actionSelectUp : api.up, #these are the same as up and down. But for a good user experience they should work if shift is pressed down as well.
self.mainWindow.ui.actionSelectDown : api.down,
self.mainWindow.ui.actionSelectUpOctave : api.upOctave,
self.mainWindow.ui.actionSelectUpOctave : api.upOctave, #these are the same as up and down. But for a good user experience they should work if shift is pressed down as well.
self.mainWindow.ui.actionSelectDownOctave : api.downOctave,
@ -228,9 +231,7 @@ class MenuActionDatabase(object):
self.mainWindow.ui.actionMulti_Measure_Rest : lambda: api.insertMultiMeasureRest(1),
self.mainWindow.ui.actionCustom_Multi_Measure_Rest : SecondaryMultimeasureRestMenu(self.mainWindow),
self.mainWindow.ui.actionMetrical_Instruction : SecondaryMetricalInstructionMenu(self.mainWindow),
#self.mainWindow.ui.actionCustom_Metrical_Instruction : lambda: CustomMetricalInstructionWidget(self.mainWindow),
self.mainWindow.ui.actionCustom_Metrical_Instruction : api.metricalTest,
#self.mainWindow.ui.actionCustom_Metrical_Instruction : lambda: CustomMetricalInstructionWidget(self.mainWindow), This was set setVisible(False) at the end of init!
self.mainWindow.ui.actionTempo_Change : lambda: SecondaryTempoChangeMenu(self.mainWindow),
self.mainWindow.ui.actionDelete_Tempo_Change : api.removeCurrentTempoItem,
@ -425,6 +426,8 @@ class MenuActionDatabase(object):
self.mainWindow.ui.centralwidget.addAction(action) #no actions without a widget
action.triggered.connect(function)
self.mainWindow.ui.actionCustom_Metrical_Instruction.setVisible(False) #TODO
self.mainWindow.ui.menubar.removeAction(self.mainWindow.ui.menuGeneric.menuAction()) #thats why we added the actions to the centralwidget above.
self.loadToolbarContext("notation")

Loading…
Cancel
Save