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.
 
 

232 lines
10 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
from template.helper import onlyOne
#Our Modules
from .constantsAndConfigs import constantsAndConfigs
from .scorescene import GuiScore
from .submenus import GridRhytmEdit
import engine.api as api
class ScoreView(QtWidgets.QGraphicsView):
def __init__(self, mainWindow):
super().__init__()
self.mainWindow = mainWindow
self.scoreScene = GuiScore(parentView=self)
self.setScene(self.scoreScene)
opengl = True
if opengl:
#Scrolling without openGl is sluggish. This is a sure improvement. -2022.
from PyQt5 import QtOpenGL
viewport = QtWidgets.QOpenGLWidget()
#viewport = QtOpenGL.QGLWidget(QtOpenGL.QGLFormat(QtOpenGL.QGL.SampleBuffers))
#These special parameters should not matter. Run with the default.
viewportFormat = QtGui.QSurfaceFormat()
viewportFormat.setSwapInterval(0) #disable VSync
viewportFormat.setDefaultFormat(viewportFormat)
viewport.setFormat(viewportFormat)
self.setViewport(viewport)
self.viewport().setAutoFillBackground(False)
self.setAlignment(QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop)
#self.setDragMode(QtWidgets.QGraphicsView.RubberBandDrag)
#self.setDragMode(QtWidgets.QGraphicsView.ScrollHandDrag)
self.setDragMode(QtWidgets.QGraphicsView.NoDrag)
api.callbacks.setCursor.append(self.centerOnCursor) #returns a dict
self._lastSavedMode = None #CC, Blocks, Notes. for self.updateMode
api.callbacks.updateBlockTrack.append(self.updateModeSingleTrackRedraw) # We need this after every update because the track is redrawn after each update and we don't know what mode-components to show
style = """
QScrollBar:horizontal {
border: 1px solid black;
}
QScrollBar::handle:horizontal {
background: #00b2b2;
}
QScrollBar:vertical {
border: 1px solid black;
}
QScrollBar::handle:vertical {
background: #00b2b2;
}
"""
self.setStyleSheet(style)
def wheelEvent(self, event):
if QtWidgets.QApplication.keyboardModifiers() in (QtCore.Qt.ControlModifier, QtCore.Qt.ControlModifier|QtCore.Qt.ShiftModifier): #a workaround for a qt bug. see score.wheelEvent docstring.
event.ignore() #do not send to scene, but tell the mainWindow to use it.
else:
super().wheelEvent(event) #send to scene
def centerOnCursor(self, cursorExportObject):
if (not constantsAndConfigs.followPlayhead) or not api.playbackStatus():
self.centerOn(self.scoreScene.cursor.scenePos())
#discard cursorExportObject.
def toggleFollowPlayhead(self):
constantsAndConfigs.followPlayhead = not constantsAndConfigs.followPlayhead
self.mainWindow.ui.actionFollow_Playhead.setChecked(constantsAndConfigs.followPlayhead)
#we register a callback in self init that checks constantsAndConfigs.followPlayhead
def stretchXCoordinates(self, factor):
"""Cumulative factor, multiplication"""
self.scoreScene.stretchXCoordinates(factor)
self.centerOnCursor(None)
def zoom(self, scaleFactor:float):
"""Scale factor is absolute"""
self.resetTransform()
self.scale(scaleFactor, scaleFactor)
def toggleNoteheadsRectangles(self):
"""Each notehead/rectangle toggles its own state.
That means each GuiChord gets toggled individually.
Both versions, notehead and rectangle, exist all the time, so
nothing gets recreated, just visibility toggled.
Rectangles/Noteheads are their own realm and do not conflict
with CC View toggle or other view modes."""
constantsAndConfigs.noteHeadMode = not constantsAndConfigs.noteHeadMode
self.mainWindow.ui.actionToggle_Notehead_Rectangles.setChecked(not constantsAndConfigs.noteHeadMode)
self.scoreScene.toggleNoteheadsRectangles()
def _switchToRectanglesForFunction(self, function):
"""some actions like actionVelocityMore, actionVelocityLess,
actionDurationModMore, actionDurationModLess,
actionReset_Velocity_Duration_Mod can be called even if
not in rectangle mode. We switch into rectangle mode for them
so the user can see the changes."""
if not self.mainWindow.ui.actionToggle_Notehead_Rectangles.isChecked():
self.toggleNoteheadsRectangles()
function() #this is most likely an api function
def resizeEvent(self, event):
"""Triggers at least when the window is resized"""
self.scoreScene.grid.reactToresizeEventOrZoom()
super().resizeEvent(event)
def changeGridRhythm(self):
GridRhytmEdit(mainWindow=self.mainWindow) #handles everything.
def mode(self):
"""Return the current edit mode as string.
Mostly needed for structures blockAt and other
functions that need to find the target of a mouse click."""
if self.mainWindow.ui.actionCC_Mode.isChecked():
return "cc"
elif self.mainWindow.ui.actionNotation_Mode.isChecked():
return "notation"
elif self.mainWindow.ui.actionBlock_Mode.isChecked():
return "block"
else:
raise ValueError("Edit Mode unknown")
def updateModeSingleTrackRedraw(self, trackId:int, trackExport:tuple):
"""trackExport is a tuple of block export dicts"""
self.scoreScene.updateModeSingleTrackRedraw(nameAsString=self.mode(), trackId=trackId, trackExport=trackExport)
def updateMode(self, *args):
"""Switch through different views for editing:
notes and item edit
CC curves
note-blocks only (without items)
Which mode is active depends entirely on the state of qt checkboxes in the menu.
The menu-actions call this function. We make sure and double sure that there is never
ambiguity in the menu or in the program mode itself.
If you want to check for the mode in any place of the program use self.mode().
It is just above this function.
Therefore: Every mode switch is only allowed through this function.
There is no direct setting of the mode. You should call the menu
action directly if you want to programatically change the mode.
Different functions and callbacks call this as an update, and they just send their arguments,
which have different types and formats. We don't care since our only information is the menu
state and the variables we set ourselves.
This is rarely called without arguments, manually, on program start just to
filter out the unused part.
"""
assert onlyOne((self.mainWindow.ui.actionCC_Mode.isChecked(), self.mainWindow.ui.actionNotation_Mode.isChecked(), self.mainWindow.ui.actionBlock_Mode.isChecked()))
if self.mainWindow.ui.actionData_Editor.isChecked():
#no modechange in the track editor
return
calledByMenu = args==(True,) # and not by track update or manually on e.g. program start to only show the right portions of the scene
if self.mainWindow.ui.actionCC_Mode.isChecked():
#if calledByMenu and self._lastSavedMode == "block":
# self.stretchXCoordinates(4.0) #return from half sized mode. If we do not come from block mode this is not needed. CC and Note mode have the same scaling
self.mainWindow.menuActionDatabase.ccEditMode()
self.mainWindow.menuActionDatabase.writeProtection(True)
self.scoreScene.updateMode("cc")
self.mainWindow.menuActionDatabase.loadToolbarContext("cc")
elif self.mainWindow.ui.actionNotation_Mode.isChecked():
#if calledByMenu and self._lastSavedMode == "block":
# self.stretchXCoordinates(4.0) #return from half sized mode. If we do not come from block mode this is not needed. CC and Note mode have the same scaling
self.mainWindow.menuActionDatabase.noteEditMode()
self.mainWindow.menuActionDatabase.writeProtection(False)
self.scoreScene.updateMode("notation")
self.mainWindow.menuActionDatabase.loadToolbarContext("notation")
elif self.mainWindow.ui.actionBlock_Mode.isChecked():
assert calledByMenu, "We currently assume that only the program start calls this function not via the menu, and that chooses notation mode"
#if not self._lastSavedMode == "block": #check that we don't go from block mode to block mode again, since XScaling is cumulative
# self.stretchXCoordinates(0.25) #go into small scaling mode. 0.25 is hardcoded and the same as scene.updateModeSingleTrackRedraw
self.mainWindow.menuActionDatabase.noteEditMode()
self.mainWindow.menuActionDatabase.writeProtection(True)
self.scoreScene.updateMode("block")
self.mainWindow.menuActionDatabase.loadToolbarContext("block")
else:
raise ValueError("Edit Mode unknown")
self._lastSavedMode = self.mode()