diff --git a/CHANGELOG b/CHANGELOG index 6b18b48..55ff374 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -7,6 +7,7 @@ External contributors notice at the end of the line: (LastName, FirstName / nick ## 2022-10-15 2.2.0 Add SOLO functionality alongside the existing audible/mute layer. Control via shortcuts, track editor, or track list widget +New function to create a new empty block from an existing one, that has reserved duration same as the original one. Add more time signatures to the quick-insert dialog: 8/4, three variants of 7/8, two variants of 5/4 and more. Also reorder and better labels. Fix splitting of notes that were created by a previous split. Block Mode: Fix invisible block labels and graphics when dragging blocks (adopting to unannounced Qt regressions once again) diff --git a/engine/api.py b/engine/api.py index 1cb9b53..0c74710 100644 --- a/engine/api.py +++ b/engine/api.py @@ -965,7 +965,7 @@ def duplicateBlock(blockId, times = 1): callbacks._updateTrack(id(track)) #callbacks._setCursor() -def duplicateContentLinkBlock(blockId, times = 1): +def duplicateContentLinkBlock(blockId, times=1): track, block = session.data.blockById(blockId) session.history.register(lambda i=id(track), l=track.asListOfBlockIds(): rearrangeBlocks(i, l), descriptionString = "content link block") for i in range(times): @@ -973,6 +973,23 @@ def duplicateContentLinkBlock(blockId, times = 1): callbacks._updateTrack(id(track)) #callbacks._setCursor() #this will make the GUI jump around because it centers on the cursor +def duplicateToReservedSpaceBlock(blockId, times=1): + """Create a new block that has the same duration as the given one. + The duration will be reserved. + Intended for the composition phase. + For example used to create a template for a second voice, where the total duration + is already known and can be reserved, so the following blocks are in the right place.""" + track, block = session.data.blockById(blockId) + session.history.register(lambda i=id(track), l=track.asListOfBlockIds(): rearrangeBlocks(i, l), descriptionString = "duplicate block as reserved space") + for i in range(times): + track.duplicateToReservedSpaceBlock(block) + callbacks._updateTrack(id(track)) + +def duplicateToReservedSpaceCurrentBlock(): + currentBlockId = id(session.data.currentTrack().currentBlock()) + duplicateToReservedSpaceBlock(currentBlockId, 1) #handles callbacks and undo + + def moveBlockToOtherTrack(blockId, newTrackId, listOfBlockIdsForNewTrack): """First move the block to the new track and then rearrange both tracks @@ -1106,15 +1123,6 @@ def moveCurrentBlockToStartOfTrack(): moveBlockToStartOfTrack(id(session.data.currentTrack().currentBlock())) -#deprecated -def _setBlockData(block, newData): - """DEPRECATED. This did not work. The linked blocks were not reduced. - At point of the bug reports, instead of fixing this, we used - duplicate block and delete which already provides all functionality""" - session.history.register(lambda bl=block, old=block.data: _setBlockData(bl, old), descriptionString = "set block data") - block.data = newData - #no callbacks needed. - #Cursor def getCursorPitch(): diff --git a/engine/block.py b/engine/block.py index 84af06e..ff1ad8b 100644 --- a/engine/block.py +++ b/engine/block.py @@ -151,7 +151,7 @@ class Block(object): else: self._parentTrack = None - def copy(self, newParentTrack): + def copy(self, newParentTrack, nameSuffix="-copy"): """Return an independet copy of this block. It will not be inserted into a track here but in the parentTrack @@ -174,10 +174,10 @@ class Block(object): if self in copyItem.parentBlocks: #TODO: investigate copyItem.parentBlocks.remove(self) - if self.name.endswith("-copy"): #name is mutable, but getter and setter deal with that. + if self.name.endswith(nameSuffix): #name is mutable, but getter and setter deal with that. new.name = self.name else: - new.name = self.name + "-copy" + new.name = self.name + nameSuffix new._minimumInTicks = self._minimumInTicks[:] #it is a mutable value, we make a copy of the list (which has an int inside, which is immutable and copied automatically) return new diff --git a/engine/track.py b/engine/track.py index 26a9137..5df38b4 100644 --- a/engine/track.py +++ b/engine/track.py @@ -481,6 +481,20 @@ class Track(object): self.blocks.insert(index +1, linked) self.toPosition(originalPosition) #has head() in it + def duplicateToReservedSpaceBlock(self, block): + """We actually do a copy and then remove the content. + While at the moment of creation (v2.2.0) it would be equally well to create a new empty + block and set the duration and name this way is better for future functionality.""" + originalPosition = self.state.position() + index = self.blocks.index(block) + copy = block.copy(newParentTrack=self, nameSuffix="-reserved") + + copy.data = list() #empty + copy.minimumInTicks = self.duration() + + self.blocks.insert(index +1, copy) + self.toPosition(originalPosition) #has head() in it + def hasContentLinks(self): return any(len(block.linkedContentBlocks) > 1 for block in self.blocks) diff --git a/qtgui/designer/mainwindow.py b/qtgui/designer/mainwindow.py index cf59bbb..50495b1 100644 --- a/qtgui/designer/mainwindow.py +++ b/qtgui/designer/mainwindow.py @@ -697,6 +697,8 @@ class Ui_MainWindow(object): self.actionToggle_Track_Solo_Playback.setObjectName("actionToggle_Track_Solo_Playback") self.actionReset_all_Solo = QtWidgets.QAction(MainWindow) self.actionReset_all_Solo.setObjectName("actionReset_all_Solo") + self.actionCreate_reserved_duration_empty_block_from_current = QtWidgets.QAction(MainWindow) + self.actionCreate_reserved_duration_empty_block_from_current.setObjectName("actionCreate_reserved_duration_empty_block_from_current") self.menuObjects.addAction(self.actionMetrical_Instruction) self.menuObjects.addAction(self.actionCustom_Metrical_Instruction) self.menuObjects.addAction(self.actionClef) @@ -789,6 +791,7 @@ class Ui_MainWindow(object): self.menuTracks.addAction(self.actionSplit_Current_Block) self.menuTracks.addAction(self.actionAppend_Block) self.menuTracks.addAction(self.actionDuplicate) + self.menuTracks.addAction(self.actionCreate_reserved_duration_empty_block_from_current) self.menuTracks.addAction(self.actionUnlink_Current_Block) self.menuTracks.addAction(self.actionCreate_Linked_Copy) self.menuTracks.addAction(self.actionDelete_Current_Block) @@ -1068,3 +1071,4 @@ class Ui_MainWindow(object): self.actionToggle_Track_Solo_Playback.setText(_translate("MainWindow", "Toggle Track Solo Playback")) self.actionToggle_Track_Solo_Playback.setShortcut(_translate("MainWindow", "Z")) self.actionReset_all_Solo.setText(_translate("MainWindow", "Reset Solo for all Tracks")) + self.actionCreate_reserved_duration_empty_block_from_current.setText(_translate("MainWindow", "Create reserved-duration empty block from current")) diff --git a/qtgui/designer/mainwindow.ui b/qtgui/designer/mainwindow.ui index e0012dd..654c62a 100644 --- a/qtgui/designer/mainwindow.ui +++ b/qtgui/designer/mainwindow.ui @@ -168,6 +168,7 @@ + @@ -1876,6 +1877,11 @@ Reset Solo for all Tracks + + + Create reserved-duration empty block from current + + diff --git a/qtgui/menu.py b/qtgui/menu.py index 9ce8037..97e7651 100644 --- a/qtgui/menu.py +++ b/qtgui/menu.py @@ -277,6 +277,7 @@ class MenuActionDatabase(object): self.mainWindow.ui.actionSplit_Current_Block : api.splitBlock, self.mainWindow.ui.actionJoin_with_next_Block : api.joinBlock, self.mainWindow.ui.actionDuplicate : api.duplicateCurrentBlock, + self.mainWindow.ui.actionCreate_reserved_duration_empty_block_from_current : api.duplicateToReservedSpaceCurrentBlock, self.mainWindow.ui.actionCreate_Linked_Copy : api.duplicateContentLinkCurrentBlock, self.mainWindow.ui.actionUnlink_Current_Block : api.unlinkCurrentBlock, self.mainWindow.ui.actionDelete_Current_Block : api.deleteCurrentBlock, diff --git a/qtgui/musicstructures.py b/qtgui/musicstructures.py index f63794d..c3d647a 100644 --- a/qtgui/musicstructures.py +++ b/qtgui/musicstructures.py @@ -175,6 +175,7 @@ class GuiBlockHandle(QtWidgets.QGraphicsRectItem): ("separator", None), #("split here", lambda: self.splitHere(event)), #Impossible because we can't see notes. (translate("musicstructures", "duplicate"), lambda: api.duplicateBlock(self.staticExportItem["id"])), + (translate("musicstructures", "duplicate to reserved empty"), lambda: api.duplicateToReservedSpaceBlock(self.staticExportItem["id"])), (translate("musicstructures", "create content link"), lambda: api.duplicateContentLinkBlock(self.staticExportItem["id"])), (translate("musicstructures", "unlink"), lambda: api.unlinkBlock(self.staticExportItem["id"])), (translate("musicstructures", "move to start"), lambda: api.moveBlockToStartOfTrack(self.staticExportItem["id"])),