You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
486 lines
25 KiB
486 lines
25 KiB
#! /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")
|
|
|
|
#Standard Library
|
|
|
|
#Third party
|
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
|
translate = QtCore.QCoreApplication.translate
|
|
|
|
#Template
|
|
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
|
|
import engine.api as api
|
|
|
|
|
|
class TrackWidget(QtWidgets.QGroupBox):
|
|
#TODO: ideas: number of blocks, list of block names which CCs are set, review/change durationSettingsSignature, dynamicSettingsSignature
|
|
def __init__(self, parentDataEditor, trackExportObject):
|
|
super().__init__()
|
|
self.parentDataEditor = parentDataEditor
|
|
self.trackExportObject = trackExportObject
|
|
self.ui = Ui_trackGroupWidget()
|
|
self.ui.setupUi(self)
|
|
|
|
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
|
|
|
|
self.trackExportObject = trackExportObject #updated on every self.updateData
|
|
self.ui.visibleCheckbox.clicked.connect(self.visibleToggled) #only user changes, not through setChecked()
|
|
self.ui.audibleCheckbox.clicked.connect(self.audibleToggled) #only user changes, not through setChecked()
|
|
self.ui.doubleTrackCheckbox.clicked.connect(self.doubleTrackToggled) #only user changes, not through setChecked()
|
|
#self.ui.upbeatSpinBox.editingFinished.connect(self.upbeatChanged) #only user changes, not through setText() etc.
|
|
self.ui.upbeatSpinBox.valueChanged.connect(self.upbeatChanged) #also through the tickWidget
|
|
self.ui.callTickWidget.clicked.connect(self.callClickWidgetForUpbeat)
|
|
self.ui.nameLineEdit.editingFinished.connect(self.nameChanged) #only user changes, not through setText() etc.
|
|
self.ui.deleteButton.clicked.connect(lambda: api.deleteTrack(self.trackExportObject["id"]))
|
|
|
|
self.ui.midiChannelSpinBox.valueChanged.connect(self.dataChanged)
|
|
self.ui.midiProgramSpinBox.valueChanged.connect(self.dataChanged)
|
|
self.ui.midiBankMsbSpinBox.valueChanged.connect(self.dataChanged)
|
|
self.ui.midiBankLsbSpinBox.valueChanged.connect(self.dataChanged)
|
|
self.ui.midiTransposeSpinBox.valueChanged.connect(self.dataChanged)
|
|
|
|
self.ui.instrumentName.editingFinished.connect(self.nameChanged)
|
|
self.ui.shortInstrumentName.editingFinished.connect(self.nameChanged)
|
|
|
|
|
|
#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 = {}
|
|
ccChannelMenu = QtWidgets.QMenu()
|
|
for i in range(1,17): #excluding 17
|
|
checkbox = QtWidgets.QCheckBox(ccChannelMenu)
|
|
self.ccChannels[i] = checkbox
|
|
checkbox.stateChanged.connect(self.dataChanged)
|
|
widget = QtWidgets.QWidget()
|
|
layout = QtWidgets.QFormLayout()
|
|
layout.setContentsMargins(0,0,0,0) #left, top, right, bottom in pixel
|
|
widget.setLayout(layout)
|
|
widgetAction = QtWidgets.QWidgetAction(ccChannelMenu)
|
|
widgetAction.setDefaultWidget(widget)
|
|
layout.addRow(str(i).zfill(2), checkbox)
|
|
ccChannelMenu.addAction(widgetAction)
|
|
|
|
#action = QtWidgets.QAction(str(i), ccChannelMenu)
|
|
#action.setCheckable(True)
|
|
#ccChannelMenu.addAction(action)
|
|
self.ui.ccChannelsPushButton.setMenu(ccChannelMenu)
|
|
|
|
#TODO: The callbacks below trigger quite often. If you move the mouse wheel in a spin box each increase/decrease sends ALL track settings to the backend which triggers a compelte redraw of the track. This should be measure on performance and improved. But even incremental updates should have an immediate effect on a running playback so "send changes when closing the track editor" is only a compromise". Incremental updates are not a a solution either because even those need a playback update. valueChanged does not have an editingFinished signal like text fields.
|
|
|
|
#Set up the advanced properties. They should be hidden at the start
|
|
#Tab 1: Durations
|
|
self.ui.defaultOn.editingFinished.connect(self.dataChanged) #only user changes, not through setText() etc.
|
|
self.ui.defaultOff.editingFinished.connect(self.dataChanged)
|
|
self.ui.staccatoOn.editingFinished.connect(self.dataChanged)
|
|
self.ui.staccatoOff.editingFinished.connect(self.dataChanged)
|
|
self.ui.tenutoOn.editingFinished.connect(self.dataChanged)
|
|
self.ui.tenutoOff.editingFinished.connect(self.dataChanged)
|
|
self.ui.legatoOn.editingFinished.connect(self.dataChanged)
|
|
self.ui.legatoOff.editingFinished.connect(self.dataChanged)
|
|
|
|
#Tab 2: Dynamics
|
|
self.ui.dynamics_ppppp.valueChanged.connect(self.dataChanged)
|
|
self.ui.dynamics_pppp.valueChanged.connect(self.dataChanged)
|
|
self.ui.dynamics_ppp.valueChanged.connect(self.dataChanged)
|
|
self.ui.dynamics_pp.valueChanged.connect(self.dataChanged)
|
|
self.ui.dynamics_p.valueChanged.connect(self.dataChanged)
|
|
self.ui.dynamics_mp.valueChanged.connect(self.dataChanged)
|
|
self.ui.dynamics_mf.valueChanged.connect(self.dataChanged)
|
|
self.ui.dynamics_f.valueChanged.connect(self.dataChanged)
|
|
self.ui.dynamics_ff.valueChanged.connect(self.dataChanged)
|
|
self.ui.dynamics_fff.valueChanged.connect(self.dataChanged)
|
|
self.ui.dynamics_ffff.valueChanged.connect(self.dataChanged)
|
|
self.ui.dynamics_custom.valueChanged.connect(self.dataChanged)
|
|
self.ui.dynamics_tacet.valueChanged.connect(self.dataChanged)
|
|
self.ui.dynamics_fp.valueChanged.connect(self.dataChanged)
|
|
self.ui.dynamics_sp.valueChanged.connect(self.dataChanged)
|
|
self.ui.dynamics_spp.valueChanged.connect(self.dataChanged)
|
|
self.ui.dynamics_sfz.valueChanged.connect(self.dataChanged)
|
|
self.ui.dynamics_sf.valueChanged.connect(self.dataChanged)
|
|
self.ui.dynamics_sff.valueChanged.connect(self.dataChanged)
|
|
|
|
self.ui.buttonResetDurations.clicked.connect(lambda: api.resetDuationSettingsSignature(self.trackExportObject["id"]))
|
|
self.ui.buttonResetDynamics.clicked.connect(lambda: api.resetDynamicSettingsSignature(self.trackExportObject["id"]))
|
|
|
|
#Connect the hide checkbox
|
|
self.ui.advanced.toggled.connect(self.advancedToggled)
|
|
self.ui.advanced.setChecked(False)
|
|
|
|
def visibleToggled(self, signal):
|
|
assert signal == bool(self.ui.visibleCheckbox.checkState())
|
|
if signal:
|
|
api.unhideTrack(self.trackExportObject["id"])
|
|
else:
|
|
api.hideTrack(self.trackExportObject["id"])
|
|
|
|
def audibleToggled(self, signal):
|
|
assert signal == bool(self.ui.audibleCheckbox.checkState())
|
|
api.trackAudible(self.trackExportObject["id"], signal)
|
|
|
|
def doubleTrackToggled(self, signal):
|
|
api.setDoubleTrack(self.trackExportObject["id"], signal)
|
|
|
|
def advancedToggled(self, bool):
|
|
assert bool == self.ui.advanced.isChecked()
|
|
if bool:
|
|
self.ui.advancedContent.show()
|
|
else:
|
|
self.ui.advancedContent.hide()
|
|
|
|
@contextmanager
|
|
def blockUiSignals(self):
|
|
"""prevent loops by blocking all signals that react on changed states.
|
|
Revoked at the end of the calling function.
|
|
Strictly this is not needed for signals like .clicked() (compared to .toggled()) but it is
|
|
a good last line of defense to prevent future bugs. Those signal-loops are hard to track.
|
|
|
|
ADD ALL NEW SUB WIDGETS HERE!!!!! THIS HAPPENED TWICED ALREADY AND WAS A NIGHTMARE TO DEBUG!
|
|
SIMPLY USING self.blockSignals(True) HAS NO EFFECT BECAUSE THOSE ARE IN .ui.
|
|
self.setUpdatesEnabled(False) doesn't work as well.
|
|
self.children() does not return the right children either, self.ui() is a python object,
|
|
not a qt widget."""
|
|
|
|
for widget in self.ui.__dict__.values():
|
|
widget.blockSignals(True)
|
|
for checkbox in self.ccChannels.values():
|
|
checkbox.blockSignals(True)
|
|
yield
|
|
for widget in self.ui.__dict__.values():
|
|
widget.blockSignals(False)
|
|
for checkbox in self.ccChannels.values():
|
|
checkbox.blockSignals(False)
|
|
|
|
def callClickWidgetForUpbeat(self):
|
|
dialog = TickWidget(self.parentDataEditor, initValue = self.ui.upbeatSpinBox.value())
|
|
self.ui.upbeatSpinBox.setValue(dialog.ui.ticks.value())
|
|
|
|
def nameChanged(self):
|
|
"""When enter is pressed or focus is lost"""
|
|
t = self.ui.nameLineEdit.text()
|
|
name = self.ui.instrumentName.text()
|
|
short = self.ui.shortInstrumentName.text()
|
|
api.setTrackName(self.trackExportObject["id"], nameString = t, initialInstrumentName = name, initialShortInstrumentName = short)
|
|
|
|
def upbeatChanged(self):
|
|
"""When enter is pressed or focus is lost"""
|
|
v = self.ui.upbeatSpinBox.value()
|
|
api.setTrackUpbeat(self.trackExportObject["id"], v)
|
|
|
|
def dataChanged(self):
|
|
"""Our data changed. Send to Engine"""
|
|
with self.blockUiSignals():
|
|
dictionary = {}
|
|
dictionary["initialMidiChannel"] = self.ui.midiChannelSpinBox.value()-1
|
|
dictionary["initialMidiProgram"] = self.ui.midiProgramSpinBox.value()
|
|
dictionary["initialMidiBankMsb"] = self.ui.midiBankMsbSpinBox.value()
|
|
dictionary["initialMidiBankLsb"] = self.ui.midiBankLsbSpinBox.value()
|
|
dictionary["ccChannels"] = tuple(chanNum-1 for chanNum, checkbox in self.ccChannels.items() if checkbox.checkState()) #can be unsorted
|
|
dictionary["midiTranspose"] = self.ui.midiTransposeSpinBox.value()
|
|
|
|
dictionary["duration.defaultOn"] = self.ui.defaultOn.text()
|
|
dictionary["duration.defaultOff"] = self.ui.defaultOff.text()
|
|
dictionary["duration.staccatoOn"] = self.ui.staccatoOn.text()
|
|
dictionary["duration.staccatoOff"] = self.ui.staccatoOff.text()
|
|
dictionary["duration.tenutoOn"] = self.ui.tenutoOn.text()
|
|
dictionary["duration.tenutoOff"] = self.ui.tenutoOff.text()
|
|
dictionary["duration.legatoOn"] = self.ui.legatoOn.text()
|
|
dictionary["duration.legatoOff"] = self.ui.legatoOff.text()
|
|
|
|
dictionary["dynamics.ppppp"] = self.ui.dynamics_ppppp.value()
|
|
dictionary["dynamics.pppp"] = self.ui.dynamics_pppp.value()
|
|
dictionary["dynamics.ppp"] = self.ui.dynamics_ppp.value()
|
|
dictionary["dynamics.pp"] = self.ui.dynamics_pp.value()
|
|
dictionary["dynamics.p"] = self.ui.dynamics_p.value()
|
|
dictionary["dynamics.mp"] = self.ui.dynamics_mp.value()
|
|
dictionary["dynamics.mf"] = self.ui.dynamics_mf.value()
|
|
dictionary["dynamics.f"] = self.ui.dynamics_f.value()
|
|
dictionary["dynamics.ff"] = self.ui.dynamics_ff.value()
|
|
dictionary["dynamics.fff"] = self.ui.dynamics_fff.value()
|
|
dictionary["dynamics.ffff"] = self.ui.dynamics_ffff.value()
|
|
dictionary["dynamics.custom"] = self.ui.dynamics_custom.value()
|
|
dictionary["dynamics.tacet"] = self.ui.dynamics_tacet.value()
|
|
dictionary["dynamics.fp"] = self.ui.dynamics_fp.value()
|
|
dictionary["dynamics.sp"] = self.ui.dynamics_sp.value()
|
|
dictionary["dynamics.spp"] = self.ui.dynamics_spp.value()
|
|
dictionary["dynamics.sfz"] = self.ui.dynamics_sfz.value()
|
|
dictionary["dynamics.sf"] = self.ui.dynamics_sf.value()
|
|
dictionary["dynamics.sff"] = self.ui.dynamics_sff.value()
|
|
|
|
if not self.trackExportObject == dictionary: #checks for keys and values
|
|
api.setTrackSettings(self.trackExportObject["id"], dictionary)
|
|
#else: no change
|
|
|
|
def updateData(self, trackExportObject):
|
|
"""Receives api updates. Change GUI fields accordingly"""
|
|
self.trackExportObject = trackExportObject
|
|
|
|
with self.blockUiSignals():
|
|
self.trackExportObject = trackExportObject
|
|
self.ui.upbeatSpinBox.setValue(trackExportObject["upbeatInTicks"])
|
|
self.ui.nameLineEdit.setText(trackExportObject["name"])
|
|
self.ui.midiChannelSpinBox.setValue(trackExportObject["initialMidiChannel"]+1)
|
|
self.ui.midiProgramSpinBox.setValue(trackExportObject["initialMidiProgram"])
|
|
self.ui.midiBankMsbSpinBox.setValue(trackExportObject["initialMidiBankMsb"])
|
|
self.ui.midiBankLsbSpinBox.setValue(trackExportObject["initialMidiBankLsb"])
|
|
self.ui.midiTransposeSpinBox.setValue(trackExportObject["midiTranspose"])
|
|
self.ui.instrumentName.setText(trackExportObject["initialInstrumentName"])
|
|
self.ui.shortInstrumentName.setText(trackExportObject["initialShortInstrumentName"])
|
|
|
|
self.ui.defaultOn.setText(trackExportObject["duration.defaultOn"])
|
|
self.ui.defaultOff.setText(trackExportObject["duration.defaultOff"])
|
|
self.ui.staccatoOn.setText(trackExportObject["duration.staccatoOn"])
|
|
self.ui.staccatoOff.setText(trackExportObject["duration.staccatoOff"])
|
|
self.ui.tenutoOn.setText(trackExportObject["duration.tenutoOn"])
|
|
self.ui.tenutoOff.setText(trackExportObject["duration.tenutoOff"])
|
|
self.ui.legatoOn.setText(trackExportObject["duration.legatoOn"])
|
|
self.ui.legatoOff.setText(trackExportObject["duration.legatoOff"])
|
|
|
|
self.ui.dynamics_ppppp.setValue(trackExportObject["dynamics.ppppp"])
|
|
self.ui.dynamics_pppp.setValue(trackExportObject["dynamics.pppp"])
|
|
self.ui.dynamics_ppp.setValue(trackExportObject["dynamics.ppp"])
|
|
self.ui.dynamics_pp.setValue(trackExportObject["dynamics.pp"])
|
|
self.ui.dynamics_p.setValue(trackExportObject["dynamics.p"])
|
|
self.ui.dynamics_mp.setValue(trackExportObject["dynamics.mp"])
|
|
self.ui.dynamics_mf.setValue(trackExportObject["dynamics.mf"])
|
|
self.ui.dynamics_f.setValue(trackExportObject["dynamics.f"])
|
|
self.ui.dynamics_ff.setValue(trackExportObject["dynamics.ff"])
|
|
self.ui.dynamics_fff.setValue(trackExportObject["dynamics.fff"])
|
|
self.ui.dynamics_ffff.setValue(trackExportObject["dynamics.ffff"])
|
|
self.ui.dynamics_custom.setValue(trackExportObject["dynamics.custom"])
|
|
self.ui.dynamics_tacet.setValue(trackExportObject["dynamics.tacet"])
|
|
self.ui.dynamics_fp.setValue(trackExportObject["dynamics.fp"])
|
|
self.ui.dynamics_sp.setValue(trackExportObject["dynamics.sp"])
|
|
self.ui.dynamics_spp.setValue(trackExportObject["dynamics.spp"])
|
|
self.ui.dynamics_sfz.setValue(trackExportObject["dynamics.sfz"])
|
|
self.ui.dynamics_sf.setValue(trackExportObject["dynamics.sf"])
|
|
self.ui.dynamics_sff.setValue(trackExportObject["dynamics.sff"])
|
|
|
|
if trackExportObject["initialMidiProgram"] >= 0:
|
|
self.ui.midiBankMsbSpinBox.setEnabled(True)
|
|
self.ui.midiBankLsbSpinBox.setEnabled(True)
|
|
else:
|
|
self.ui.midiBankMsbSpinBox.setEnabled(False)
|
|
self.ui.midiBankLsbSpinBox.setEnabled(False)
|
|
|
|
#hidden position can be 0 as well. So don't bool, check for None explicitely.
|
|
if trackExportObject["hiddenPosition"] is None: #visible
|
|
self.ui.visibleCheckbox.setChecked(True)
|
|
self.ui.deleteButton.setEnabled(True)
|
|
else:
|
|
self.ui.visibleCheckbox.setChecked(False)
|
|
self.ui.deleteButton.setEnabled(False) #You cannot delete hidden tracks. see api.deleteTrack
|
|
|
|
self.ui.doubleTrackCheckbox.setChecked(trackExportObject["double"])
|
|
self.ui.audibleCheckbox.setChecked(trackExportObject["audible"])
|
|
|
|
for i in range(0,16): #without 16
|
|
#Engine from 0 to 15, GUI from 1 to 16.
|
|
self.ccChannels[i+1].setChecked(i in trackExportObject["ccChannels"]) #a checkbox widget.
|
|
|
|
|
|
|
|
class TrackEditor(QtWidgets.QWidget):
|
|
"""Created by ControlMainWindow. One permanent instance"""
|
|
def __init__(self, mainWindow):
|
|
super().__init__()
|
|
self.mainWindow = mainWindow
|
|
|
|
self.layout = QtWidgets.QVBoxLayout()
|
|
self.layout.setAlignment(QtCore.Qt.AlignTop | QtCore.Qt.AlignLeft)
|
|
self.setLayout(self.layout)
|
|
|
|
api.callbacks.tracksChangedIncludingHidden.append(self.updateTrackWidgets)
|
|
self.tracks = {} #id:trackWidget. As any dict, not in order
|
|
|
|
#Add Track Button
|
|
self.addTrackButton = QtWidgets.QPushButton("add track placeholder text")
|
|
self.addTrackButton.clicked.connect(api.newEmptyTrack)
|
|
self.layout.addWidget(self.addTrackButton)
|
|
|
|
self.addTenTracksButton = QtWidgets.QPushButton("add ten tracks placeholder text")
|
|
self.addTenTracksButton.clicked.connect(self.addTenTracks)
|
|
self.layout.addWidget(self.addTenTracksButton)
|
|
|
|
#Upbeat Tick Widget and Action-Button
|
|
allUpbeats = QtWidgets.QWidget()
|
|
allUpbeatsLayout = QtWidgets.QHBoxLayout()
|
|
allUpbeatsLayout.setAlignment(QtCore.Qt.AlignTop | QtCore.Qt.AlignLeft)
|
|
allUpbeats.setLayout(allUpbeatsLayout)
|
|
self.layout.addWidget(allUpbeats)
|
|
|
|
self.allUpbeatsCombinedTickWidget = CombinedTickWidget(mainWindow)
|
|
|
|
allUpbeatsPushButton = QtWidgets.QPushButton(translate("trackEditorPythonFile", "Set all Upbeats"))
|
|
allUpbeatsPushButton.clicked.connect(self.setAllUpbeats) #gets the value itself
|
|
|
|
allUpbeatsLayout.addWidget(self.allUpbeatsCombinedTickWidget)
|
|
allUpbeatsLayout.addWidget(allUpbeatsPushButton)
|
|
|
|
#Reset all Advanced Views
|
|
foldAllAdvanvced = QtWidgets.QPushButton(translate("trackEditorPythonFile", "Fold all Advanced"))
|
|
foldAllAdvanvced.clicked.connect(self.foldAllAdvanvced)
|
|
allUpbeatsLayout.addWidget(foldAllAdvanvced)
|
|
|
|
unfoldAllAdvanvced = QtWidgets.QPushButton(translate("trackEditorPythonFile", "Unfold all Advanced"))
|
|
unfoldAllAdvanvced.clicked.connect(self.unfoldAllAdvanced)
|
|
allUpbeatsLayout.addWidget(unfoldAllAdvanvced)
|
|
|
|
def addTenTracks(self):
|
|
for i in range(10):
|
|
api.newEmptyTrack()
|
|
|
|
def updateTrackWidgets(self, listOfStaticTrackRepresentations):
|
|
"""React to the backend adding, deleting, hiding or moving tracks.
|
|
A track widget persists until the track gets deleted. No re-creation on every change. """
|
|
leftOver = list(self.tracks.keys())
|
|
visibleTracks = []
|
|
trackWidgetsInOrder = []
|
|
nameLineEditsInOrder = []
|
|
upbeatSpinBoxsInOrder = []
|
|
for trackExportObject in listOfStaticTrackRepresentations:
|
|
#First are all normal, visible tracks.
|
|
#After that the tracks have a property "hiddenPosition" for hidden tracks that need individual sorting.
|
|
if not trackExportObject["id"] in self.tracks: #A brand new track
|
|
widget = TrackWidget(self, trackExportObject)
|
|
self.tracks[trackExportObject["id"]] = widget
|
|
self.layout.insertWidget(-1, widget)
|
|
widget.setTitle(f'Track {trackExportObject["index"]+1} - id: {trackExportObject["id"]}')
|
|
else:
|
|
leftOver.remove(trackExportObject["id"])
|
|
|
|
self.tracks[trackExportObject["id"]].ui.deleteButton.setEnabled(True)
|
|
self.tracks[trackExportObject["id"]].blockSignals(True)
|
|
self.tracks[trackExportObject["id"]].updateData(trackExportObject) #This has a sideeffect of updating widgets, which will change backend data and trigger a recursive updateTrackWidgets. We need to block the signals. self.tracks has the track widgets.
|
|
self.tracks[trackExportObject["id"]].blockSignals(False)
|
|
nameLineEditsInOrder.append(self.tracks[trackExportObject["id"]].ui.nameLineEdit)
|
|
|
|
trackWidgetsInOrder.append(self.tracks[trackExportObject["id"]])
|
|
if trackExportObject["hiddenPosition"] is None:
|
|
visibleTracks.append(trackExportObject)
|
|
|
|
numberOfTracks = trackExportObject["index"]+1
|
|
self.addTrackButton.setText(translate("trackEditorPythonFile", "Add new Track (currently: ") + str(numberOfTracks) + ")")
|
|
self.addTenTracksButton.setText(translate("trackEditorPythonFile", "Add 10 new Tracks (currently: ") + str(numberOfTracks) + ")")
|
|
|
|
for trId in leftOver: #track still exist here but not in the backend. Delete.
|
|
w = self.tracks[trId]
|
|
del self.tracks[trId]
|
|
w.setParent(None)
|
|
w.close()
|
|
|
|
#Sort tracks as they are in the backend/score-view
|
|
for widget in trackWidgetsInOrder: #visibleTracks is sorted and does not have empty tracks.
|
|
self.layout.removeWidget(widget)
|
|
for widget in trackWidgetsInOrder:
|
|
#Re-Insert all, this time in order.
|
|
self.layout.insertWidget(-1, widget)
|
|
|
|
#Create tab order. Those work in pairs, linked list. a before b. And multiple of those pairs need to be in order itself.
|
|
if len(nameLineEditsInOrder) > 1:
|
|
for pair in pairwise(nameLineEditsInOrder):
|
|
self.setTabOrder(*pair) #takes exactly two arguments
|
|
|
|
if len(visibleTracks) == 1:
|
|
w = self.tracks[visibleTracks[0]["id"]]
|
|
w.ui.deleteButton.setEnabled(False) #it is not possible to delete the only visible track. The backend will prevent it but we don't even offer the choice here.
|
|
|
|
def setAllUpbeats(self):
|
|
for trackWidget in self.tracks.values():
|
|
trackWidget.ui.upbeatSpinBox.setValue(self.allUpbeatsCombinedTickWidget.value())
|
|
|
|
def foldAllAdvanvced(self):
|
|
for trackWidget in self.tracks.values():
|
|
trackWidget.ui.advanced.setChecked(False)
|
|
|
|
def unfoldAllAdvanced(self):
|
|
for trackWidget in self.tracks.values():
|
|
trackWidget.ui.advanced.setChecked(True)
|
|
|
|
|
|
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))
|
|
|
|
class TrackBlockScene(QtWidgets.QGraphicsScene):
|
|
def __init__(self, parentWidget):
|
|
super().__init__()
|
|
self.blocks = []
|
|
self.parentWidget = parentWidget
|
|
|
|
|
|
def mousePressEvent(self, event):
|
|
button = event.button()
|
|
if button == 1: #left
|
|
self.setEnabled(False)
|
|
super().mousePressEvent(event)
|
|
|
|
def mouseReleaseEvent(self, event):
|
|
button = event.button()
|
|
if button == 1: #left
|
|
self.setEnabled(True)
|
|
super().mouseReleaseEvent(event)
|
|
|
|
def regenerateBlocks(self, blocksExportData):
|
|
self.clear()
|
|
self.blocks = []
|
|
|
|
dur = 0
|
|
for backendBlock in blocksExportData:
|
|
b = BlockGraphicsItem(self, backendBlock)
|
|
self.blocks.append(b)
|
|
self.addItem(b)
|
|
b.setPos(backendBlock["tickindex"] / constantsAndConfigs.blocksTicksToPixelRatio, 0)
|
|
dur += backendBlock["completeDuration"]
|
|
|
|
self.parentWidget.ui.blocks.setFixedSize(dur / constantsAndConfigs.blocksTicksToPixelRatio, 30)
|
|
|
|
|
|
class BlockGraphicsItem(QtWidgets.QGraphicsRectItem):
|
|
def __init__(self, parentTrackWidget, blockExportObject):
|
|
width = blockExportObject["completeDuration"] / constantsAndConfigs.blocksTicksToPixelRatio
|
|
super().__init__(0,0,width, 30) #x, y, w, h
|
|
|
|
self.setBrush(self.stringToColor(blockExportObject["name"]))
|
|
#self.setFlags(QtWidgets.QGraphicsItem.ItemSendsGeometryChanges|QtWidgets.QGraphicsItem.ItemIsFocusable)
|
|
#self.setCursor(QtCore.Qt.SizeAllCursor)
|
|
|
|
def stringToColor(self, st):
|
|
if st:
|
|
c = md5(st.encode()).hexdigest()
|
|
return QtGui.QColor(int(c[0:9],16) % 255, int(c[10:19],16) % 255, int(c[20:29],16)% 255, 255)
|
|
else:
|
|
return QtGui.QColor(255,255,255,255) #Return White
|
|
"""
|
|
|