#! /usr/bin/env python3 # -*- coding: utf-8 -*- """ Copyright 2022, Nils Hilbricht, Germany ( https://www.hilbricht.net ) This file is part of Patroneo ( https://www.laborejo.org ) Laborejo 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") SIZE_UNIT = 30 #this is in manual sync with songeditor.py SIZE_UNIT import engine.api as api #Session is already loaded and created, no duplication. from PyQt5 import QtCore, QtGui, QtWidgets 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. self.backColor = QtGui.QColor(55, 61, 69) self.setBackgroundBrush(self.backColor) class TimelineRect(QtWidgets.QGraphicsRectItem): """Shows information about song progression. JACK transport only shares the current time. We cannot draw anything ahead of time other than what we know ourselves. We rely on external tempo information and cannot react to tempo changes. Our linear value is measures, so we display these.""" def __init__(self, parentScene): 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 c = self.parentScene.parentView.parentMainWindow.fPalBlue.color(role) self.setBrush(c) role = QtGui.QPalette.BrightText self.brightText = self.parentScene.parentView.parentMainWindow.fPalBlue.color(role) self._cachedSubdivisions = 1 self.measureNumbers = [] #self._buffer_measuresPerGroup set in callback_setnumberOfMeasures, changed in wheelEvent. Utilized in hoverLeaveEvent self._pressed = False api.callbacks.numberOfMeasuresChanged.append(self.callback_setnumberOfMeasures) api.callbacks.scoreChanged.append(self.callback_setnumberOfMeasures) #sends information about measuresPerGroup api.callbacks.subdivisionsChanged.append(self.cache_subdivisions) #sends information about measuresPerGroup api.callbacks.timeSignatureChanged.append(self.cache_timesignature) self.setToolTip(QtCore.QCoreApplication.translate("Timeline", "Click to set playback position. Scroll with mousewheel to adjust measure grouping.")) self.setAcceptHoverEvents(True) def hoverEnterEvent(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.")) def hoverLeaveEvent(self, event): self.statusMessage("") def cache_timesignature(self, howManyUnits, whatTypeOfUnit): self._cachedExportDictScore["howManyUnits"] = howManyUnits self._cachedExportDictScore["whatTypeOfUnit"] = whatTypeOfUnit def cache_subdivisions(self, subdivisions): self._cachedSubdivisions = subdivisions def callback_setnumberOfMeasures(self, exportDictScore): """We only draw one number and line for each group and not the barlines in between""" self._cachedExportDictScore = exportDictScore requestAmountOfMeasures = exportDictScore["numberOfMeasures"] self._buffer_measuresPerGroup = exportDictScore["measuresPerGroup"] self.setRect(0,0,requestAmountOfMeasures * SIZE_UNIT, SIZE_UNIT) #Delete old for l in self.measureNumbers: l.setParentItem(None) self.parentScene.removeItem(l) self.measureNumbers = [] #Create new for i in range(requestAmountOfMeasures+1): if i > 0 and (i+1) % exportDictScore["measuresPerGroup"] == 1: measure = QtWidgets.QGraphicsSimpleTextItem(str(i)) #str(i).zfill(3) measure.setBrush(self.brightText) measure.setParentItem(self) measure.setPos((i-1)*SIZE_UNIT, 5) #some magic pixel values for finetuning. #measure.setEnabled(False) #Contrary to intuition this will not make this item ignore mouse clicks but just eat them. Enabling fowards mouse item to the timeline below. measure.setFlag(self.ItemIgnoresTransformations) self.measureNumbers.append(measure) barline = QtWidgets.QGraphicsLineItem(0,0,0,self.height) barline.setParentItem(self) barline.setPen(self.brightText) barline.setPos(i*SIZE_UNIT, 0) #barline.setEnabled(False) #Contrary to intuition this will not make this item ignore mouse clicks but just eat them. Enabling fowards mouse item to the timeline below. barline.setFlag(self.ItemIgnoresTransformations) self.measureNumbers.append(barline) def _sendPlaybackPositionToEngine(self, posX): oneMeasureInTicks = ( self._cachedExportDictScore["howManyUnits"] * self._cachedExportDictScore["whatTypeOfUnit"] ) / self._cachedSubdivisions ratio = oneMeasureInTicks / SIZE_UNIT value = posX * ratio api.seek(int(value)) def mousePressEvent(self, event): if event.button() == QtCore.Qt.LeftButton: event.accept() self._sendPlaybackPositionToEngine(event.scenePos().x()) self._pressed = True def mouseMoveEvent(self, event): if self._pressed: self._sendPlaybackPositionToEngine(event.scenePos().x()) event.accept() def mouseReleaseEvent(self, event): if event.button() == QtCore.Qt.LeftButton: event.accept() self._pressed = False def wheelEvent(self, event): """This buffers until hoverLeaveEvent and then the new value is sent in self.hoverLeaveEvent""" event.accept() if event.delta() > 0: self._buffer_measuresPerGroup += 1 else: self._buffer_measuresPerGroup = max(1, self._buffer_measuresPerGroup-1) api.set_measuresPerGroup(self._buffer_measuresPerGroup)