From 2f420b19bd8b8e4f964e67e6ec791da83111bba5 Mon Sep 17 00:00:00 2001 From: Nils Date: Sat, 14 May 2022 19:24:11 +0200 Subject: [PATCH] Switch deleteOnIdle to a lower timer level and use a python loop to clean up instead of the qtimer itself in freewheeling mode. This hopefully improves performance --- qtgui/scorescene.py | 24 +++++++++++++++-------- qtgui/scoreview.py | 46 ++++++++++++++++++++++++++++++++++++++------- 2 files changed, 55 insertions(+), 15 deletions(-) diff --git a/qtgui/scorescene.py b/qtgui/scorescene.py index 6668acd..5b22b18 100644 --- a/qtgui/scorescene.py +++ b/qtgui/scorescene.py @@ -48,9 +48,9 @@ class GuiScore(QtWidgets.QGraphicsScene): self.tracks = {} #trackId:guiTrack, #if we don't save the instances here in Python space Qt will loose them and they will not be displayed without any error message. - self.deleteOnIdleStack = [] # a stack that holds hidden items that need to be deleted. Hiding is much faster than deleting so we use that for the blocking function. Since we always recreate items and never re-use this is ok as a list. no need for a set. + self.deleteOnIdleStack = set() # a stack that holds hidden items that need to be deleted. Hiding is much faster than deleting so we use that for the blocking function. self._deleteOnIdleLoop = QtCore.QTimer() - self._deleteOnIdleLoop.start(0) #0 means "if there is time" + self._deleteOnIdleLoop.start(100) #0 means "if there is time" self._deleteOnIdleLoop.timeout.connect(self._deleteOnIdle) #processes deleteOnIdleStack self.duringTrackDragAndDrop = None #switched to a QGraphicsItem (e.g. GuiTrack) while a track is moved around by the mouse @@ -106,6 +106,12 @@ class GuiScore(QtWidgets.QGraphicsScene): self.grid.updateMode(nameAsString) + 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 + def maxTrackLength(self): if self.tracks: return max(tr.lengthInPixel for tr in self.tracks.values()) @@ -123,7 +129,6 @@ class GuiScore(QtWidgets.QGraphicsScene): self.tracks[trackId].redraw(staticRepresentationList) #else: #hidden track. But this can still happen through the data editor - self.parentView.updateMode() self.updateSceneRect() def trackPaintBlockBackgroundColors(self, trackId, staticBlocksRepresentation): @@ -240,19 +245,22 @@ class GuiScore(QtWidgets.QGraphicsScene): -just hide items, never delete them is fast but gets slower the more items are hidden in the track """ item.hide() - self.deleteOnIdleStack.append(item) #This will actually delete the item and remove it from the scene when there is idle time + self.deleteOnIdleStack.add(item) #This will actually delete the item and remove it from the scene when there is idle time def _deleteOnIdle(self): """Hiding a QGraphicsItem is much faster than removing it from the scene. To keep the GUI responsive but also to get rid of the old data we hide in createGraphicItemsFromData and whenever there is time the actual removing and deleting will happen here. - This function is connected to a timer(0) defined in self init. + This function is connected to a timer defined in self init. """ - if self.deleteOnIdleStack: - deleteMe = self.deleteOnIdleStack.pop() + #if self.deleteOnIdleStack: + # deleteMe = self.deleteOnIdleStack.pop() + # self.removeItem(deleteMe) #This is the only line in the program that should call scene.removeItem + # del deleteMe + for deleteMe in self.deleteOnIdleStack: self.removeItem(deleteMe) #This is the only line in the program that should call scene.removeItem - del deleteMe + self.deleteOnIdleStack = set() def trackAt(self, qScenePosition): diff --git a/qtgui/scoreview.py b/qtgui/scoreview.py index 04113a8..8aa7233 100644 --- a/qtgui/scoreview.py +++ b/qtgui/scoreview.py @@ -65,7 +65,8 @@ class ScoreView(QtWidgets.QGraphicsView): 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._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 { @@ -86,6 +87,8 @@ class ScoreView(QtWidgets.QGraphicsView): """ 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. @@ -103,6 +106,7 @@ class ScoreView(QtWidgets.QGraphicsView): #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) @@ -134,6 +138,14 @@ class ScoreView(QtWidgets.QGraphicsView): self.toggleNoteheadsRectangles() function() #this is most likely an api function + def resizeEvent(self, event): + """Triggers mostly when new notes are entered""" + 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 @@ -147,13 +159,10 @@ class ScoreView(QtWidgets.QGraphicsView): else: raise ValueError("Edit Mode unknown") - def resizeEvent(self, event): - """Triggers mostly when new notes are entered""" - self.scoreScene.grid.reactToresizeEventOrZoom() - super().resizeEvent(event) + 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 changeGridRhythm(self): - GridRhytmEdit(mainWindow=self.mainWindow) #handles everything. def updateMode(self, *args): """Switch through different views for editing: @@ -171,6 +180,14 @@ class ScoreView(QtWidgets.QGraphicsView): 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())) @@ -179,22 +196,37 @@ class ScoreView(QtWidgets.QGraphicsView): #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()