Add status bar to explain possible user actions (like "use shift + mousewheel to transpose measure")
Streamline mousewheel behaviour in song editor. It now always scrolls without shift / alt key, no more accidental transpositions.
Fix wrong playback cursor speed.
2021-02-15 Version 2.1.0
Full Undo/Redo
Add track groups, which double as midi bus. This enhances overview for more complex projects.
Add option to follow the playhead in pattern view (or not), required by the much longer patterns. Also better scrolling and playhead is always visible now.
self.color=colorifcolorelse"#00FFFF"# "#rrggbb" in hex. no alpha. a convenience slot for the GUI to save a color.
self.patternLengthMultiplicator=1#int. >= 1 the multiplicator is added after all other calculations, like subdivions. We can't integrate this into howManyUnits because that is the global score value
self.midiChannel=0# 0-15 midi channel is always set.
self.group=""# "" is a standalone track. Using a name here will group these tracks together. A GUI can use this information. Also all tracks in a group share a single jack out port.
self.visible=True#only used together with groups. the api and our Datas setGroup function take care that standalone tracks are never hidden.
api.session.data.setLanguageForEmptyFile(language=QtCore.QLocale().languageToString(QtCore.QLocale().language()))#TODO: this is a hack because we access the session directly. But this is also a function tied to Qts language string. Two wrongs...
self.start()#This shows the GUI, or not, depends on the NSM gui save setting. We need to call that after the menu, otherwise the about dialog will block and then we get new menu entries, which looks strange.
@ -260,7 +265,7 @@ class MainWindow(TemplateMainWindow):
#Functions depend on getting set after getting called. They need to know the old track!
self.currentTrackId=newCurrentTrackId
defaddTrack(self):
defaddPatternTrack(self):
"""Add a new track and initialize it with some data from the current one"""
scale=api.session.data.trackById(self.currentTrackId).pattern.scale#TODO: Access to the sessions data structure directly instead of api. Not good. Getter function or api @property is cleaner.
api.addTrack(scale)
@ -298,11 +303,13 @@ class MainWindow(TemplateMainWindow):
"""Called once at the creation of the GUI"""
self.ui.toolBar.contextMenuEvent=api.nothing#remove that annoying Checkbox context menu
self.ui.actionClone_Selected_Track.setToolTip(QtCore.QCoreApplication.translate("Toolbar","Use this! Create a new track that inherits everything but the content from the original. Already jack connected!"))
self.ui.actionAddTrack.setToolTip(QtCore.QCoreApplication.translate("Toolbar","Add a complete empty track that needs to be connected to an instrument manually."))
self.ui.actionAddPattern.setToolTip(QtCore.QCoreApplication.translate("Toolbar","Add a complete empty track that needs to be connected to an instrument manually."))
spacer()
self.ui.toolBar.addWidget(beatsPerMinuteBlock)# combined widget with its label and translation included
#if not type(self.itemAt(event.scenePos().x(), event.scenePos().y(), self.parentView.transform())) is Step:
# self.showVelocities()
else:
event.ignore()
super().mousePressEvent(event)
@ -467,10 +470,11 @@ class PatternGrid(QtWidgets.QGraphicsScene):
step.setApperance()
classStep(QtWidgets.QGraphicsRectItem):
"""The representation of a note"""
def__init__(self,parentScene,column,row):#Factor and Velocity are set on activation
self.parentScene=parentScene
self.statusMessage=self.parentScene.parentView.parentMainWindow.statusBar().showMessage#a version with the correct path of this is in every class of Patroneo
self.column=column#grid coordinates, not pixels
self.row=row
offset=2
@ -500,6 +504,8 @@ class Step(QtWidgets.QGraphicsRectItem):
self.setApperance()#sets color, size and exceedPlayback warning. not velocity.
defsetApperance(self):
"""sets color, main/sub size and exceedPlayback warning. not velocity.
@ -639,8 +645,10 @@ class Step(QtWidgets.QGraphicsRectItem):
ifthemousecursorisnotonthatitemanymore"""
ifself.status:
event.accept()
self.statusMessage(QtCore.QCoreApplication.translate("Statusbar","Note: Left click do deactivate. Middle click to listen. MouseWheel to change volume. Right click for pattern options."))
self._rememberVelocity=self.velocity
else:
self.statusMessage(QtCore.QCoreApplication.translate("Statusbar","Left click do activate note. Middle click to listen. Right click for pattern options."))
event.ignore()
defhoverLeaveEvent(self,event):
@ -648,6 +656,7 @@ class Step(QtWidgets.QGraphicsRectItem):
@ -656,7 +665,6 @@ class Step(QtWidgets.QGraphicsRectItem):
else:
event.ignore()
defwheelEvent(self,event):
"""This buffers until hoverLeaveEvent and then the new value is sent in self.hoverLeaveEvent"""
ifself.status:
@ -681,11 +689,19 @@ class Scale(QtWidgets.QGraphicsRectItem):
def__init__(self,parentScene):
super().__init__(0,0,0,0)
self.parentScene=parentScene
self.statusMessage=self.parentScene.parentView.parentMainWindow.statusBar().showMessage#a version with the correct path of this is in every class of Patroneo
self.pitchWidgets=[]#sorted from top to bottom in Step Rect and scene coordinates
self.simpleNoteNames=None#list of 128 notes. use index with note name. Can be changed at runtime. Never empty.
#self.buildScale(1) #also sets the positions of the buttons above
self.setAcceptHoverEvents(True)
defhoverEnterEvent(self,event):
self.statusMessage(QtCore.QCoreApplication.translate("Statusbar","Pitch in MIDI half-tones. 60 = middle C. Enter number or spin the mouse wheel to change."))
#Order matters. We need to set the notenames before the scale.
self.buildScale(exportDict["numberOfSteps"])
@ -1007,6 +1023,8 @@ class VelocityControls(QtWidgets.QWidget):
self.parentScene=patternScene
self.mainWindow=mainWindow
self.statusMessage=self.parentScene.parentView.parentMainWindow.statusBar().showMessage#a version with the correct path of this is in every class of Patroneo
layout=QtWidgets.QHBoxLayout()
layout.setSpacing(0)
@ -1035,9 +1053,11 @@ class VelocityControls(QtWidgets.QWidget):
self.parentScene.showVelocities()
defenterEvent(self,event):
self.statusMessage(QtCore.QCoreApplication.translate("Statusbar","Click to change volume for all notes in single steps, spin mouse wheel to change in steps of 10."))
self.parentScene.showVelocities()
defleaveEvent(self,event):
self.statusMessage("")
self.parentScene.hideVelocities()
defvelocityUp(self):
@ -1049,7 +1069,3 @@ class VelocityControls(QtWidgets.QWidget):
@ -233,6 +246,7 @@ class TrackStructure(QtWidgets.QGraphicsRectItem):
def__init__(self,parentScene):
super().__init__(0,0,1,SIZE_UNIT)
self.parentScene=parentScene
self.statusMessage=self.parentScene.parentView.parentMainWindow.statusBar().showMessage#a version with the correct path of this is in every class of Patroneo
self.setAcceptHoverEvents(True)#for the preview highlight switch
self.exportDict=None#self.update gets called immediately after creation.
@ -491,10 +505,12 @@ class TrackStructure(QtWidgets.QGraphicsRectItem):
defhoverEnterEvent(self,event):
self._highlightSwitch.show()
self.statusMessage(QtCore.QCoreApplication.translate("Statusbar","Empty Measure: Left click to activate. Middle click to show as shadows in current pattern. Right click for measure group options."))#Yes, this is the track. Empty measures are not objects.
#This seemed to be a good idea but horrible UX. If you move the mouse down to edit a pattern you end up choosing the last track
@ -540,6 +556,7 @@ class Switch(QtWidgets.QGraphicsRectItem):
"""
def__init__(self,parentTrackStructure,position):
self.parentTrackStructure=parentTrackStructure
self.statusMessage=self.parentTrackStructure.parentScene.parentView.parentMainWindow.statusBar().showMessage#a version with the correct path of this is in every class of Patroneo
self.position=position
super().__init__(0,0,SIZE_UNIT,SIZE_UNIT)
@ -622,11 +639,14 @@ class Switch(QtWidgets.QGraphicsRectItem):
event.ignore()
defhoverEnterEvent(self,event):
"""Only active switches"""
self.statusMessage(QtCore.QCoreApplication.translate("Statusbar","Measure: Left click to deactivate. Middle click to show as shadows in current pattern. Shift+MouseWheel for half tone transposition. Alt+MouseWheel for in-scale transposition. Right click for measure group options."))
#Scale Transpose. Independent of Halftone Transpose
@ -672,13 +692,12 @@ class Switch(QtWidgets.QGraphicsRectItem):
event.ignore()
#super.wheelEvent(event)
classTrackLabelEditor(QtWidgets.QGraphicsScene):
"""Only the track labels"""
"""Only the track labels: names, colors, groups"""
def__init__(self,parentView):
super().__init__()
self.parentView=parentView
self.statusMessage=self.parentView.parentMainWindow.statusBar().showMessage#a version with the correct path of this is in every class of Patroneo
self.tracks={}#TrackID:TrackLabel
self.groups=[]#GroupLabel()
@ -884,6 +903,8 @@ class TrackLabel(QtWidgets.QGraphicsRectItem):
def__init__(self,parentScene,width,height):
super().__init__(0,0,width,height)
self.parentScene=parentScene
self.statusMessage=self.parentScene.parentView.parentMainWindow.statusBar().showMessage#a version with the correct path of this is in every class of Patroneo
self.setPen(QtGui.QPen(QtCore.Qt.NoPen))
self.setFlag(self.ItemIgnoresTransformations)#zoom will repostion but not make the font bigger.
@ -892,7 +913,7 @@ class TrackLabel(QtWidgets.QGraphicsRectItem):
@ -914,6 +935,7 @@ class TrackLabel(QtWidgets.QGraphicsRectItem):
super().__init__()
self.parentTrackLabel=parentTrackLabel
self.setAcceptHoverEvents(True)
self.spinBox=QtWidgets.QSpinBox()
self.spinBox.setSuffix("x")
#self.spinBox.setFrame(True)
@ -921,6 +943,11 @@ class TrackLabel(QtWidgets.QGraphicsRectItem):
self.setWidget(self.spinBox)
self.spinBox.valueChanged.connect(self.spinBoxValueChanged)#Callback for setting is in ParentTrackLabel.update
defhoverEnterEvent(self,event):
self.parentTrackLabel.statusMessage(QtCore.QCoreApplication.translate("Statusbar","Measure length multiplicator. Enter number or spin the mouse wheel to change."))
@ -1077,6 +1126,7 @@ class GroupLabel(QtWidgets.QGraphicsRectItem):
super().__init__(0,0,width,height)
self.parentScene=parentScene
self.statusMessage=self.parentScene.parentView.parentMainWindow.statusBar().showMessage#a version with the correct path of this is in every class of Patroneo
self.group=name
self.visible=visible#if that changes it will change only on creation of a GroupLabel instance
self.setPen(QtGui.QPen(QtCore.Qt.NoPen))
@ -1092,7 +1142,7 @@ class GroupLabel(QtWidgets.QGraphicsRectItem):
self.qLabel.setStyleSheet("background-color: rgba(0,0,0,0)")#transparent so we see the RectItem color
defmousePressEvent(self,event):
self.setAcceptHoverEvents(True)
defhoverEnterEvent(self,event):
self.statusMessage(QtCore.QCoreApplication.translate("Statusbar","Track Group: Double Click to show or hide. You can also double click the empty group spacers above the tracks."))
defhoverLeaveEvent(self,event):
self.statusMessage("")
defmouseDoubleClickEvent(self,event):
"""Without this no PositionHandle mouseMove and mouse Release events!!!
Alsonodoubleclick"""
#super().mousePressEvent(event)
@ -1122,6 +1179,7 @@ class GroupLabel(QtWidgets.QGraphicsRectItem):
@ -31,6 +31,7 @@ class Timeline(QtWidgets.QGraphicsScene):
def__init__(self,parentView):
super().__init__()
self.parentView=parentView
self.statusMessage=self.parentView.parentMainWindow.statusBar().showMessage#a version with the correct path of this is in every class of Patroneo
self.addItem(TimelineRect(parentScene=self))
#Set color, otherwise it will be transparent in window managers or wayland that want that.
@ -49,6 +50,7 @@ class TimelineRect(QtWidgets.QGraphicsRectItem):
self.height=25
super().__init__(0,0,1,self.height)
self.parentScene=parentScene
self.statusMessage=self.parentScene.parentView.parentMainWindow.statusBar().showMessage#a version with the correct path of this is in every class of Patroneo
self._cachedExportDictScore={}
role=QtGui.QPalette.Light
@ -71,6 +73,12 @@ class TimelineRect(QtWidgets.QGraphicsRectItem):
self.setToolTip(QtCore.QCoreApplication.translate("Timeline","Click to set playback position. Scroll with mousewheel to adjust measure grouping."))
self.setAcceptHoverEvents(True)
defhoverEnterEvent(self,event):
self.statusMessage(QtCore.QCoreApplication.translate("Statusbar","Timeline: Click to set playback position. Scroll with mousewheel to adjust measure grouping. Right click on measures below for options to use these groups."))