#! /usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Copyright 2019 , Nils Hilbricht , Germany ( https : / / www . hilbricht . net )
This file is part of the Laborejo Software Suite ( https : / / www . laborejo . org ) ,
more specifically its template base application .
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 ; logging . info ( " import {} " . format ( __file__ ) )
#Third Party
from PyQt5 import QtCore , QtGui , QtWidgets , QtOpenGL
#from PyQt5 import QtOpenGL
#Template Modules
from template . helper import onlyOne
#Our Modules
from . submenus import GridRhytmEdit
from . constantsAndConfigs import constantsAndConfigs
from . structures import GuiScore
import engine . api as api
class ScoreView ( QtWidgets . QGraphicsView ) :
def __init__ ( self , mainWindow ) :
super ( ) . __init__ ( )
self . mainWindow = mainWindow
#OpenGL has a huge positive impact on performance
if True : #for testing
viewport = QtOpenGL . QGLWidget ( QtOpenGL . QGLFormat ( QtOpenGL . QGL . SampleBuffers ) )
viewport . format ( ) . setSwapInterval ( 0 ) #disable VSync.
viewport . setAutoFillBackground ( False )
viewport = QtWidgets . QOpenGLWidget ( )
viewportFormat = QtGui . QSurfaceFormat ( )
viewportFormat . setSwapInterval ( 0 ) #disable VSync
#viewportFormat.setSamples(2**8) #By default, the highest number of samples available is used.
viewportFormat . setDefaultFormat ( viewportFormat )
viewport . setFormat ( viewportFormat )
self . setViewport ( viewport )
self . setAlignment ( QtCore . Qt . AlignLeft | QtCore . Qt . AlignTop )
#self.setDragMode(QtWidgets.QGraphicsView.RubberBandDrag)
self . setDragMode ( QtWidgets . QGraphicsView . NoDrag )
self . scoreScene = GuiScore ( self )
self . setScene ( self . scoreScene )
api . callbacks . setCursor . append ( self . centerOnCursor ) #returns a dict
api . callbacks . updateBlockTrack . append ( self . updateMode ) # We need this after every update because the track is redrawn after each update and we don't know what to show
self . xFactor = 1 #keep track of the x stretch factor.
style = """
QScrollBar : horizontal {
border : 1 px solid black ;
}
QScrollBar : : handle : horizontal {
background : #00b2b2;
}
QScrollBar : vertical {
border : 1 px solid black ;
}
QScrollBar : : handle : vertical {
background : #00b2b2;
}
"""
self . setStyleSheet ( style )
self . _zoom ( ) #no parameters, uses config values
def resizeEvent ( self , event ) :
self . scoreScene . grid . reactToresizeEventOrZoom ( )
super ( ) . resizeEvent ( event )
def changeGridRhythm ( self ) :
GridRhytmEdit ( mainWindow = self . mainWindow ) #handles everything.
def centerOnCursor ( self , cursorExportObject ) :
if ( not constantsAndConfigs . followPlayhead ) or not api . playbackStatus ( ) :
self . centerOn ( self . scoreScene . cursor . scenePos ( ) )
#discard cursorExportObject.
def wheelEvent ( self , ev ) :
modifiers = QtWidgets . QApplication . keyboardModifiers ( )
if modifiers == QtCore . Qt . ControlModifier :
if ev . angleDelta ( ) . y ( ) > 0 :
self . widen ( )
else :
self . shrinken ( )
ev . accept ( )
elif modifiers == QtCore . Qt . ControlModifier | QtCore . Qt . ShiftModifier :
if ev . angleDelta ( ) . y ( ) > 0 :
self . zoomIn ( )
else :
self . zoomOut ( )
ev . accept ( )
else :
super ( ) . wheelEvent ( ev ) #send to the items
def _zoom ( self ) :
api . session . guiSharedDataToSave [ " zoom_factor " ] = constantsAndConfigs . zoomFactor
self . resetTransform ( )
self . scoreScene . grid . setVisible ( constantsAndConfigs . zoomFactor > = 1 )
self . scale ( constantsAndConfigs . zoomFactor , constantsAndConfigs . zoomFactor )
self . centerOnCursor ( None )
def zoomIn ( self ) :
constantsAndConfigs . zoomFactor = round ( constantsAndConfigs . zoomFactor + 0.25 , 2 )
if constantsAndConfigs . zoomFactor > 2.5 :
constantsAndConfigs . zoomFactor = 2.5
self . _zoom ( )
return True
def zoomOut ( self ) :
constantsAndConfigs . zoomFactor = round ( constantsAndConfigs . zoomFactor - 0.25 , 2 )
if constantsAndConfigs . zoomFactor < constantsAndConfigs . maximumZoomOut :
constantsAndConfigs . zoomFactor = constantsAndConfigs . maximumZoomOut
self . _zoom ( )
return True
def zoomNull ( self ) :
constantsAndConfigs . zoomFactor = 1
self . _zoom ( )
def _stretchXCoordinates ( self , factor ) :
""" Reposition the items on the X axis.
Call goes through all parents / children , starting from here .
The parent sets the X coordinates of its children .
Then the parent calls the childs _stretchXCoordinates ( ) method if the child has children
itself . For example a rectangleItem has a position which is set by the parent . But the
rectangleItem has a right border which needs to be adjusted as well . This right border is
treated as child of the rectItem , handled by rectItem . _stretchXCoordinates ( factor ) .
"""
self . xFactor * = factor
constantsAndConfigs . ticksToPixelRatio / = factor
api . session . guiSharedDataToSave [ " ticks_to_pixel_ratio " ] = constantsAndConfigs . ticksToPixelRatio
self . scoreScene . stretchXCoordinates ( factor )
self . centerOnCursor ( None )
return True
def widen ( self ) :
self . _stretchXCoordinates ( 1 * 1.2 ) #2 is also good
def shrinken ( self ) :
self . _stretchXCoordinates ( 1 / 1.2 ) #0.5 is also good
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 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 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 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 .
"""
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
if self . mainWindow . ui . actionCC_Mode . isChecked ( ) :
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 ( ) :
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 ( ) :
self . mainWindow . menuActionDatabase . noteEditMode ( )
self . mainWindow . menuActionDatabase . writeProtection ( True )
self . scoreScene . updateMode ( " block " )
self . mainWindow . menuActionDatabase . loadToolbarContext ( " block " )
else :
raise ValueError ( " Edit Mode unknown " )