#! /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 . """ import logging; logger = logging.getLogger(__name__); logger.info("import") #Third Party from PyQt5 import QtCore, QtGui, QtWidgets #Template Modules #Our Modules import engine.api as api class TrackListWidget(QtWidgets.QWidget): """Small container wrapper for a title, checkboxes etc.""" def __init__(self, mainWindow, parentSplitter): super().__init__(parentSplitter) self.mainWindow = mainWindow self.layout = QtWidgets.QVBoxLayout(self) self._currentlySelectedCursorExportDict = None self.trackLabel = QtWidgets.QLabel("Hello") self.showEverythingCheckBox = QtWidgets.QCheckBox(QtCore.QCoreApplication.translate("TrackListWidget", "Show Everything")) self.showEverythingCheckBox.setChecked(True) self.showEverythingCheckBox.toggled.connect(self.reactShowEverything) self.soloPlaybackCheckbox = QtWidgets.QCheckBox(QtCore.QCoreApplication.translate("TrackListWidget", "Playback Solo")) self.soloPlaybackCheckbox.toggled.connect(self.reactToggleSolo) self.realTrackListWidget = _TrackListWidget(mainWindow, self) self.layout.addWidget(self.trackLabel) self.layout.addWidget(self.soloPlaybackCheckbox) self.layout.addWidget(self.showEverythingCheckBox) self.layout.addWidget(self.realTrackListWidget, stretch=1) api.callbacks.setCursor.append(self.callback_setCursor) def reactShowEverything(self, newState:bool): self.realTrackListWidget.showEverything(newState) def callback_setCursor(self, c:dict): self._currentlySelectedCursorExportDict = c self.trackLabel.setText( "{}-{}".format(c["trackIndex"]+1, c["trackName"]) ) self.soloPlaybackCheckbox.blockSignals(True) #we don't want to trigger reactToggleSolo. Even if that is not recursive (qt prevents signals when nothing actually changed), it is a double call to the api with the same value self.soloPlaybackCheckbox.setChecked(c["solo"]) self.soloPlaybackCheckbox.blockSignals(False) def reactToggleSolo(self, newState:bool): trId = self._currentlySelectedCursorExportDict["trackId"] api.trackSolo(trId, newState) class _TrackListWidget(QtWidgets.QListWidget): """The TrackListWidget holds all tracks as text-only variants with cursor access. It will show one track at a time, the current one. It is meant as alternative access for hard-to-reach items, such as zero-duration-items. The cursor decides which track is currently handled as items. But track content changes can come it for any track at any time. In opposite to the ScoreScene we do not deal with deleted and hidden tracks. We recreate the items on track change. """ def __init__(self, mainWindow, parentWidget): super().__init__() self.mainWindow = mainWindow self._lastCurrentTrackId = None self._firstBlockNames = {} # engineTrackId : string self.tracks = {} # engineTrackId : engineExportData self.itemPressed.connect(self._react_itemPressed) self._showEverything = True api.callbacks.tracksChanged.append(self.syncTracks) api.callbacks.updateTrack.append(self.updateTrack) api.callbacks.setCursor.append(self.setCursor) api.callbacks.updateBlockTrack.append(self.updateBlockList) #to get the block names def showEverything(self, state:bool): """Can be called externally to filter out the most common items. The indexing and cursor will still work""" self._showEverything = state self.showTrack(self._lastCurrentTrackId) def setCursor(self, cursorExportObject): if not self._lastCurrentTrackId == cursorExportObject["trackId"]: self._lastCurrentTrackId = cursorExportObject["trackId"] self.showTrack(cursorExportObject["trackId"]) self.blockSignals(True) self.setCurrentRow(cursorExportObject["position"]+1) #+1 offset for the initial block name self.blockSignals(False) def _react_itemPressed(self, item): """This is gui->api cursor setting It only happens when the mouse etc. actually pressed an item. This list cannot be activated by cursor keys etc. directly. """ i = self.currentRow() #currentRow is calculated after we already are on the item. if i > 0: i -= 1 #-1 because we have the artificial first block item that the engine didn't send us api.toPosition(i) def syncTracks(self, listOfStaticTrackRepresentations): """Handles the number of tracks and track meta-data changes, but not track contents, which is handled by self.updateTrack through a different callback""" for trackExportObject in listOfStaticTrackRepresentations: if not trackExportObject["id"] in self.tracks: self.tracks[trackExportObject["id"]] = None def updateBlockList(self, trackId, listOfBlockExportDicts): """This comes in for every small item change. But only per track. Blocknames in our list are done by using the artificial block end markers, but the first block does not have that. We store this for self.showTrack """ self._firstBlockNames[trackId] = listOfBlockExportDicts[0]["name"] #updateTrack callback arrives before updateBlockTrack. #We need both, but we also need to update the first item directly after a name change if trackId == self._lastCurrentTrackId: self.item(0).setText("== " + self._firstBlockNames[trackId] ) def updateTrack(self, trackId, staticRepresentationList): """Callback: The content of a single track has changed""" if not trackId in self.tracks: #hidden track. But this can still happen through the data editor return self.tracks[trackId] = staticRepresentationList if trackId == self._lastCurrentTrackId: self.showTrack(trackId) def showTrack(self, trackId): """Cursor moved to a different track. This is essentially destroy-and-recreate in Qt """ self.clear() staticRepresentationList = self.tracks[trackId] count = 0 #Block entries are done with BlockEndMarker, but there is no for the first. self.addItem("== " + self._firstBlockNames[self._lastCurrentTrackId]) filterTypes = set(("Chord", "Rest")) if self._showEverything: for staticItem in staticRepresentationList: self.addItem(staticItem["UIstring"]) else: #Hide Chords and Rests. The indexing and cursor will still work for staticItem in staticRepresentationList: item = QtWidgets.QListWidgetItem(staticItem["UIstring"]) self.addItem(item) if staticItem["type"] in filterTypes: item.setHidden(True) self.addItem("|") #Appending