#! /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 . """ 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 .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: 1px solid black; } QScrollBar::handle:horizontal { background: #00b2b2; } QScrollBar:vertical { border: 1px 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")