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.
189 lines
7.6 KiB
189 lines
7.6 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")
|
|
|
|
|
|
#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( "<b>{}-{}</b>".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
|
|
|