diff --git a/qtgui/grid.py b/qtgui/grid.py index 7c4dcb1..9b50bbe 100644 --- a/qtgui/grid.py +++ b/qtgui/grid.py @@ -36,6 +36,9 @@ from .constantsAndConfigs import constantsAndConfigs import engine.api as api +gridPen = QtGui.QPen(QtCore.Qt.DotLine) +gridPen.setCosmetic(True) + masterLine = QtCore.QLineF(0, 0, 0, 128*constantsAndConfigs.stafflineGap) # (x1, y1, x2, y2) class RhythmLine(QtWidgets.QGraphicsLineItem): def __init__(self, parentGrid): @@ -44,8 +47,99 @@ class RhythmLine(QtWidgets.QGraphicsLineItem): self.setParentItem(parentGrid) self.setAcceptedMouseButtons(QtCore.Qt.NoButton) #we still need this otherwise no rubberband. +class ActualGrid(QtWidgets.QGraphicsItem): + + def boundingRect(self): + return self.childrenBoundingRect() + +class GuiGrid(QtWidgets.QGraphicsItem): + """Third version of the grid. + Version 1: QGraphicsItemGroup with horizontal and vertical lines. Slow, disturbing to look at + even when the horizontal lines where nice ledger lines. + Drawing all lines for the whole score. + + Version 2: QGraphicsItemGroup only vertical lines. + Only a few lines were created and were moved on scrolling. + QGraphicsItemGroupp.addToGroup and the constant updating was still too much. + + Version 3: QGraphicsItem. Only vertical. Draw full scene. No scrolling trickery. + As simple as possible. Leave as much of the work to Qt. + We have a subitem that is actually the grid, so we can show/hide/delete that at once. + + """ + + def __init__(self, parentScene): + super(GuiGrid, self).__init__() + self.parent = parentScene #QGraphicsScene + self.setFlag(QtWidgets.QGraphicsItem.ItemHasNoContents, True) #only child items. Without this we get notImplementedError: QGraphicsItem.paint() is abstract and must be overridden + self.setAcceptedMouseButtons(QtCore.Qt.NoButton) + self.setEnabled(False) + self.setOpacity(constantsAndConfigs.gridOpacity) + self.actualGridItem = None + self.nrOfVerticalLines = 0 #remember for optimisations + self._rememberSceneHeight = 0 + + + def reactOnScoreChange(self): + """Entry point for callbacks and parent functions""" + self.redrawTickGrid() + + def boundingRect(self): + return self.childrenBoundingRect() + + def updateMode(self, nameAsString): + assert nameAsString in constantsAndConfigs.availableEditModes + if nameAsString == "block": + self.hide() + else: + self.show() + + def redrawTickGrid(self): + """A complete redraw when triggered.""" + + gapVertical = constantsAndConfigs.gridRhythm / constantsAndConfigs.ticksToPixelRatio #this is necessary every time after stretching (ticksToPixelRatio) and after changing the gridRhythm + width = self.parent.maxTrackLength() + height = self.parent.cachedSceneHeight + + lines = int(width / gapVertical) + 8 # + extra ones for a good feeling + + if lines == self.nrOfVerticalLines and self._rememberSceneHeight == height: + return + else: #change + self.nrOfVerticalLines = lines + self._rememberSceneHeight = height + + try: + self.parent.removeWhenIdle(self.actualGridItem) #remove all at once + except: + pass #will fail the first time on start + + self.actualGridItem = ActualGrid(self) + self.actualGridItem.setFlag(QtWidgets.QGraphicsItem.ItemHasNoContents, True) #only child items. Without this we get notImplementedError: QGraphicsItem.paint() is abstract and must be overridden + self.actualGridItem.setParentItem(self) + self.actualGridItem.setPos(0,0) + + for i in range(lines): + l = QtWidgets.QGraphicsLineItem(0,0,0, height, self.actualGridItem) #x1, y1, x2, y2, parent + l.setPos(i * gapVertical, 0) + + def reactToresizeEventOrZoom(self): + """Called by the Views resizeEvent. + When the views geometry changes or zooms""" + pass + + def stretchXCoordinates(self, factor): + """Reposition the items on the X axis. + Call goes through all parents/children, starting from ScoreView._stretchXCoordinates. + Docstring there.""" + gapVertical = constantsAndConfigs.gridRhythm / constantsAndConfigs.ticksToPixelRatio #this is necessary every time after stretching (ticksToPixelRatio) and after changing the gridRhythm + + for vline in self.actualGridItem.childItems(): #rhythm- and barlines + vline.setX(vline.x() * factor) + -class GuiGrid(QtWidgets.QGraphicsItemGroup): +class OldGuiGrid(QtWidgets.QGraphicsItemGroup): + #Don't use that. QGraphicsItemGroup is very slow. """The grid consists of vertical lines. lines help to estimate the rhythm or tick positions e.g. when trying to find the right place for a tempo change or CC. @@ -129,7 +223,7 @@ class GuiGrid(QtWidgets.QGraphicsItemGroup): """Only allowed to get called by reactToresizeEventOrZoom because we need an updated self.height value""" #Check if we need longer lines. Do this before creating new lines, because new lines - #have the heighest + #have the highest oldHeight = self.linesVertical[-1].line().y2() #this is a bit less than .length() so we use that if self.height > oldHeight: for vline in self.linesVertical: diff --git a/qtgui/scorescene.py b/qtgui/scorescene.py index 5b22b18..f54b9b8 100644 --- a/qtgui/scorescene.py +++ b/qtgui/scorescene.py @@ -70,12 +70,14 @@ class GuiScore(QtWidgets.QGraphicsScene): self.backColor.setNamedColor("#fdfdff") self.setBackgroundBrush(self.backColor) + self.cachedSceneHeight = 0 #set in self.redraw. Used by updateTrack to set the sceneRect + self.grid = GuiGrid(parentScene=self) self.addItem(self.grid) self.grid.setPos(0, -20 * constantsAndConfigs.stafflineGap) #this is more calculation than simply using self.yStart, and might require manual adjustment in the future, but at least it guarantees the grid matches the staffline positions self.grid.setZValue(-50) - self.cachedSceneHeight = 0 #set in self.redraw. Used by updateTrack to set the sceneRect + #All Cursors self.cursor = Cursor() @@ -109,8 +111,8 @@ class GuiScore(QtWidgets.QGraphicsScene): def updateModeSingleTrackRedraw(self, nameAsString:str, trackId:int, trackExport:tuple): """trackExport is a tuple of block export dicts""" self.tracks[trackId].updateMode(nameAsString) - if nameAsString == "block": - self.tracks[trackId].stretchXCoordinates(0.25) #go into small scaling mode. 0.25 is hardcoded and the same as scoreView.updateMode + #if nameAsString == "block": + # self.tracks[trackId].stretchXCoordinates(0.25) #go into small scaling mode. 0.25 is hardcoded and the same as scoreView.updateMode def maxTrackLength(self): if self.tracks: @@ -129,6 +131,7 @@ class GuiScore(QtWidgets.QGraphicsScene): self.tracks[trackId].redraw(staticRepresentationList) #else: #hidden track. But this can still happen through the data editor + self.grid.reactOnScoreChange() self.updateSceneRect() def trackPaintBlockBackgroundColors(self, trackId, staticBlocksRepresentation): diff --git a/qtgui/scoreview.py b/qtgui/scoreview.py index 8aa7233..0a663bb 100644 --- a/qtgui/scoreview.py +++ b/qtgui/scoreview.py @@ -139,7 +139,7 @@ class ScoreView(QtWidgets.QGraphicsView): function() #this is most likely an api function def resizeEvent(self, event): - """Triggers mostly when new notes are entered""" + """Triggers at least when the window is resized""" self.scoreScene.grid.reactToresizeEventOrZoom() super().resizeEvent(event) @@ -200,8 +200,8 @@ class ScoreView(QtWidgets.QGraphicsView): 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 + #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) @@ -209,8 +209,8 @@ class ScoreView(QtWidgets.QGraphicsView): 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 + #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) @@ -219,8 +219,8 @@ class ScoreView(QtWidgets.QGraphicsView): 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 + #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")