You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
492 lines
24 KiB
492 lines
24 KiB
#! /usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
Copyright 2017, Nils Hilbricht, Germany ( https://www.hilbricht.net )
|
|
|
|
This file is part of Laborejo ( https://www.laborejo.org )
|
|
|
|
Laborejo 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/>.
|
|
"""
|
|
|
|
from PyQt5 import QtCore, QtGui, QtSvg, QtWidgets
|
|
from .constantsAndConfigs import constantsAndConfigs
|
|
from template.qtgui.helper import stringToColor
|
|
import engine.api as api
|
|
|
|
|
|
"""This file handles the timeline which can be found at the top of the
|
|
notation editor.
|
|
|
|
Actually this is a rhythm-line since our linear value is rhythm, or
|
|
ticks, which is the same.
|
|
|
|
That just means that the space between a whole note is always the same
|
|
in pixel. Neither tempo nor any other value can change this (except
|
|
proportional zooming of course).
|
|
"""
|
|
|
|
class TimeLine(QtWidgets.QGraphicsRectItem):
|
|
|
|
def __init__(self, parentView):
|
|
|
|
#The right border of the rect gets updated through self.updateGraphBlockTrack
|
|
#Achtung! if you set y to zero and then extend the height in the minus direction it will appear correctly but the hover events will be broken in WEIRD ways. weird means the hover-rect seems to be curved and you have different hover ranger on different X-coordinates. strange stuff...
|
|
super().__init__(0, 0, 1, 15) #(x, y, w, h)
|
|
|
|
self.setAcceptHoverEvents(True)
|
|
self.setOpacity(0.75)
|
|
self.setFlag(QtWidgets.QGraphicsItem.ItemDoesntPropagateOpacityToChildren)
|
|
self.inactiveBrush = QtGui.QColor(233, 233, 233) #most of the time
|
|
self.activeBrush = QtGui.QColor(200, 200, 200)
|
|
self.setBrush(self.inactiveBrush)
|
|
self.tempoPoints = []
|
|
self.blockEndMarkers = []
|
|
self.appendGraphBlockButtonProxy = None #filled in by updateGraphBlockTrack because it adds a widget, which needs a scene which is not available during init
|
|
self.parentView = parentView
|
|
self.lastTouchedReferenceTicks = 210*2**8 #convenience. remember the last tempo unit so the user can just click on the timeline without entering a value each time.
|
|
|
|
"""
|
|
#Pen for the borders
|
|
pen = QtGui.QPen()
|
|
pen.setCapStyle(QtCore.Qt.RoundCap)
|
|
pen.setJoinStyle(QtCore.Qt.RoundJoin)
|
|
pen.setWidth(1)
|
|
pen.setColor(QtGui.QColor("Red"))
|
|
self.setPen(pen)
|
|
"""
|
|
#self.setPen(QtGui.QColor("transparent"))
|
|
|
|
api.callbacks.updateTempoTrack.append(self.createGraphicItemsFromData)
|
|
api.callbacks.updateTempoTrackBlocks.append(self.updateGraphBlockTrack)
|
|
|
|
def hoverEnterEvent(self, event):
|
|
"""TempoItems can be deleted by hovering over them and pressing
|
|
the delete key. We want to prevent that the delete key also
|
|
deletes notes. This will happen if the cursor does not exactly
|
|
hover over the tempopoint. To prevent this accident we
|
|
deactivate the scores delete key.
|
|
And show the user a different color to let them know."""
|
|
self.setBrush(self.activeBrush)
|
|
self.scene().parentView.viewport().update()
|
|
#self.scene().parentView.mainWindow.ui.actionDelete.setEnabled(False) #disable Score-Item delete
|
|
self.scene().parentView.mainWindow.menuActionDatabase.ccEditMode()
|
|
event.accept()
|
|
|
|
def hoverLeaveEvent(self, event):
|
|
"""reverse hoverEnterEvent"""
|
|
self.setBrush(self.inactiveBrush)
|
|
self.scene().parentView.viewport().update()
|
|
#self.scene().parentView.mainWindow.ui.actionDelete.setEnabled(True) #enable Score-Item delete again
|
|
self.scene().parentView.mainWindow.menuActionDatabase.noteEditMode()
|
|
event.accept()
|
|
|
|
def mousePressEvent(self, event):
|
|
if event.button() == 1: # QtCore.Qt.MouseButton.LeftButton
|
|
self.add(event.scenePos()) #create a new tempo point by telling the api a position and then reacting to "delete all, recreate" from the callback.
|
|
event.accept()
|
|
else:
|
|
super().mousePressEvent(event) #call default implementation from QGraphicsRectItem
|
|
|
|
#self.parentView.verticalScrollBar().valueChanged.connect(self.repositionAfterScroll)
|
|
#self.parentView.horizontalScrollBar().valueChanged.connect(self.repositionAfterScroll)
|
|
#def repositionAfterScroll(self):
|
|
# Dont use it. Only in here as later template for handling scrollbars.
|
|
#
|
|
#print(self, self.parentView.mapToScene(0, 0).y())
|
|
# #self.setY(self.parentView.mapToScene(0, 0).y())
|
|
# #self.setX(self.parentView.mapToScene(0, 0).x())
|
|
# self.outline.setX(self.parentView.mapToScene(0, 0).x())
|
|
# self.setY(-1*constantsAndConfigs.trackHeight)
|
|
|
|
def createAppendButton(self):
|
|
self.appendGraphBlockButtonProxy = self.scene().addWidget(QtWidgets.QPushButton("+TempoBlock"))
|
|
self.appendGraphBlockButtonProxy.widget().clicked.connect(api.appendTempoBlock)
|
|
self.appendGraphBlockButtonProxy.setParentItem(self)
|
|
self.appendGraphBlockButtonProxy.setPos(0,0) #for now. Afterwards it gets updated by updateGraphBlockTrack .
|
|
self.appendGraphBlockButtonProxy.setZValue(10) #this is only the z Value within the Timline ItemGroup
|
|
|
|
@property
|
|
def items(self):
|
|
return self.tempoPoints + self.blockEndMarkers
|
|
|
|
def createGraphicItemsFromData(self, staticRepresentationList):
|
|
"""The X position is, as always, a long tickvalue from the
|
|
backend.
|
|
The tempo value, has its default 0-line on the middle line, which is the track root.
|
|
"""
|
|
for item in self.items:
|
|
item.setParentItem(None)
|
|
self.scene().removeWhenIdle(item)
|
|
|
|
self.tempoPoints = []
|
|
self.blockEndMarkers = []
|
|
|
|
xOffset = -6 #adjust items font x offsset
|
|
y = -30 #The Y Value adjusts for the offset the text-item creates
|
|
|
|
for point in staticRepresentationList:
|
|
if not point["type"] == "interpolated": #a real user point or lastInBlock or lastInTrack
|
|
p = TempoPoint(self, point)
|
|
p.setParentItem(self)
|
|
p.setPos(point["position"] / constantsAndConfigs.ticksToPixelRatio + xOffset, y)
|
|
self.tempoPoints.append(p)
|
|
|
|
def updateGraphBlockTrack(self, staticRepresentationList):
|
|
"""Handles and visualizes block boundaries.
|
|
Also sets the dimensions for the whole timeline-rect"""
|
|
#{"type" : "GraphBlock", "id":id(block), "name":block.name, "duration":block.duration, "position":tickCounter, "exportsAllItems":block.exportsAllItems()}
|
|
|
|
self.blockListCache = staticRepresentationList #sorted list
|
|
for dictExportItem in staticRepresentationList:
|
|
blockEndMarker = TempoBlockEndMarker(self, dictExportItem)
|
|
self.blockEndMarkers.append(blockEndMarker)
|
|
#Position is the block start. We create an end-marker so we need to add the blocks duration.
|
|
blockEndMarker.setParentItem(self)
|
|
rightBorder = (dictExportItem["duration"] + dictExportItem["position"]) / constantsAndConfigs.ticksToPixelRatio
|
|
blockEndMarker.setX(rightBorder)
|
|
|
|
#rightBorder is still the last item in the list, so it is the end of the timeline
|
|
r = self.rect()
|
|
r.setRight(rightBorder)
|
|
self.setRect(r)
|
|
|
|
if not self.appendGraphBlockButtonProxy: #the append button is a widget, not a QGraphicsItem. So we can only add it after we have a complete scene.
|
|
self.createAppendButton()
|
|
self.appendGraphBlockButtonProxy.setX((dictExportItem["duration"] + dictExportItem["position"]) / constantsAndConfigs.ticksToPixelRatio + 10)
|
|
|
|
def add(self, scenePos):
|
|
"""Use a scenePos (from a mousePress) to instruct the backend
|
|
to create a new tempo point.
|
|
|
|
We must first figure out on our own in which block we want
|
|
to add the tempo point.
|
|
|
|
At the end add() calls the api to generate the new point.
|
|
This triggers self.createGraphicItemsFromData which deletes
|
|
all points and recreates them.
|
|
"""
|
|
#y
|
|
#y = scenePos.y()
|
|
#unitsPerMinute, referenceTicks = self.getYAsBackendValue(y)
|
|
unitsPerMinute = 120
|
|
referenceTicks = api.D4
|
|
|
|
#x
|
|
sp = scenePos.x() * constantsAndConfigs.ticksToPixelRatio
|
|
|
|
if constantsAndConfigs.snapToGrid:
|
|
sp = round(sp / constantsAndConfigs.gridRhythm) * constantsAndConfigs.gridRhythm
|
|
|
|
api.insertTempoItemAtAbsolutePosition(sp, unitsPerMinute, referenceTicks)
|
|
|
|
|
|
class TempoPoint(QtWidgets.QGraphicsTextItem):
|
|
"""A point where the values can be edited by the user"""
|
|
|
|
def __init__(self, parentTempoTrack, staticExportItem):
|
|
super().__init__("") #x,y,w,h
|
|
self.staticExportItem = staticExportItem
|
|
self.parentTempoTrack = parentTempoTrack
|
|
|
|
if not self.staticExportItem["positionInBlock"] == 0:
|
|
self.setAcceptHoverEvents(True)
|
|
self.setFlags(QtWidgets.QGraphicsItem.ItemIsMovable)
|
|
self.setCursor(QtCore.Qt.SizeHorCursor) #this sets the cursor while the mouse is over the item. It is independent of AcceptHoverEvents
|
|
else:
|
|
self.ungrabMouse = api.nothing #to surpress a warning from the context menu
|
|
|
|
self.setFont(constantsAndConfigs.musicFont)
|
|
self.setHtml("<font color='black' size='6'>{}</font>".format(constantsAndConfigs.realNoteDisplay[staticExportItem["referenceTicks"]]))
|
|
|
|
self.number = QtWidgets.QGraphicsTextItem("")
|
|
self.number.setParentItem(self)
|
|
self.number.setHtml("<font color='black' size='2'>{}</font>".format(str(int(staticExportItem["unitsPerMinute"]))))
|
|
self.number.setPos(0,0)
|
|
|
|
if not self.staticExportItem["graphType"] == "standalone":
|
|
self.arrow = QtWidgets.QGraphicsSimpleTextItem("⟶") #unicode long arrow right #http://xahlee.info/comp/unicode_arrows.html
|
|
self.arrow.setParentItem(self)
|
|
self.arrow.setPos(13,30)
|
|
|
|
def getXDifferenceAsBackendValue(self):
|
|
"""The problem is that we need the position relativ to its
|
|
parent block, which we don't use in that way in the GUI.
|
|
So we calculate the difference to the old position"""
|
|
|
|
oldPositionAbsolute = int(self.staticExportItem["position"])
|
|
newPositionAbsolute = self.pos().x() * constantsAndConfigs.ticksToPixelRatio
|
|
if constantsAndConfigs.snapToGrid:
|
|
newPositionAbsolute = round(newPositionAbsolute / constantsAndConfigs.gridRhythm) * constantsAndConfigs.gridRhythm
|
|
diff = int(-1 * (oldPositionAbsolute - newPositionAbsolute))
|
|
return diff
|
|
|
|
def hoverEnterEvent(self, event):
|
|
"""To prevent score-item deletion the timeline itself
|
|
has a hover event as well that deactivates the score delete
|
|
key so we can use it safely for tempoPoints"""
|
|
self.grabKeyboard()
|
|
#self.setFocus() #do NOT set the focus. GrabKeyboard is enough. The focus will stay even after hoverLeaveEvent so that Delete will delete the last hovered item. not good!
|
|
|
|
def hoverLeaveEvent(self, event):
|
|
"""reverse hoverEnterEvent"""
|
|
self.ungrabKeyboard()
|
|
|
|
def keyPressEvent(self, event):
|
|
"""Handle the delete item key. Only works if the item
|
|
has the keyboard focus. We grab that on hoverEnter."""
|
|
key = event.key()
|
|
|
|
if key == 16777223 and not self.staticExportItem["lastInBlock"] and not self.staticExportItem["positionInBlock"] == 0:
|
|
#self.scene().parentView.mainWindow.ui.actionDelete.setEnabled(True) #we never reach hoverLeave since this item gets delete. So re-enable Score-Item delete here.
|
|
api.removeTempoItem(self.staticExportItem["id"])
|
|
|
|
def mousePressEvent(self, event):
|
|
"""Pressing the mouse button is the first action of drag
|
|
and drop. We make the mouse cursor invisible so the user
|
|
can see where the point is going"""
|
|
super().mousePressEvent(event)
|
|
self.lastPos = self.pos()
|
|
#if event.button() == 1: # QtCore.Qt.MouseButton.LeftButton
|
|
# self.setCursor(QtCore.Qt.BlankCursor)
|
|
|
|
def wheelEvent(self, event):
|
|
if event.delta() > 0:
|
|
delta = 1
|
|
else:
|
|
delta = -1
|
|
api.changeTempoItem(self.staticExportItem["id"], 0, self.staticExportItem["unitsPerMinute"] + delta, self.staticExportItem["referenceTicks"])
|
|
#super().wheelEvent(event) #if this gets called the graphicView scrolls.
|
|
|
|
def mouseReleaseEvent(self, event):
|
|
"""After moving a point around
|
|
send an update to the backend"""
|
|
super().mouseReleaseEvent(event)
|
|
|
|
if event.button() == 1: # QtCore.Qt.MouseButton.LeftButton
|
|
moveInTicks = self.getXDifferenceAsBackendValue() #handles snapToGrid
|
|
self.parentTempoTrack.lastTouchedReferenceTicks = self.staticExportItem["referenceTicks"] #used in parentTempoTrack.add()
|
|
#event.scenePos().y()) is the mouseCursor, not the item. Since the cursor and the item don't need to be on the same position (the user can't even see the cursor while moving) we need the item.y()
|
|
unitsPerMinute = self.staticExportItem["unitsPerMinute"] #stays the same
|
|
referenceTicks = self.staticExportItem["referenceTicks"] #stays the same
|
|
api.changeTempoItem(self.staticExportItem["id"], moveInTicks, unitsPerMinute, referenceTicks)
|
|
|
|
def mouseMoveEvent(self, event):
|
|
"""Only active when the item is also selected and left
|
|
mouse button is down. Not any mouse event, no hover."""
|
|
super().mouseMoveEvent(event)
|
|
self.setY(self.lastPos.y()) #only the X axis can be changed. Point can only be moved left and right.
|
|
|
|
absoluteXPosition = self.x() * constantsAndConfigs.ticksToPixelRatio
|
|
withinBlock = self.staticExportItem["minPossibleAbsolutePosition"] <= absoluteXPosition <= self.staticExportItem["maxPossibleAbsolutePosition"]
|
|
if not withinBlock:
|
|
if absoluteXPosition < self.staticExportItem["minPossibleAbsolutePosition"]:
|
|
self.setX(self.staticExportItem["minPossibleAbsolutePosition"] / constantsAndConfigs.ticksToPixelRatio)
|
|
else:
|
|
self.setX(self.staticExportItem["maxPossibleAbsolutePosition"] / constantsAndConfigs.ticksToPixelRatio)
|
|
|
|
self.globalCursorToItem("x")
|
|
|
|
def globalCursorToItem(self, xOrY):
|
|
"""Make mouse cursor movement not weird when going over the boundaries.
|
|
We essentially make self the cursor that cannot leave the rectangle."""
|
|
a = self.scene().parentView.mapFromScene(self.pos())
|
|
b = self.scene().parentView.viewport().mapToGlobal(a)
|
|
|
|
if xOrY == "x":
|
|
QtGui.QCursor.setPos(b.x(), QtGui.QCursor.pos().y())
|
|
else:
|
|
QtGui.QCursor.setPos(QtGui.QCursor.pos().x(), b.y())
|
|
|
|
def contextMenuEvent(self, event):
|
|
menu = QtWidgets.QMenu()
|
|
|
|
listOfLabelsAndFunctions = [
|
|
("interpolation type", self.changeInterpolationType),
|
|
("units per minute", self.changeTempoUnits),
|
|
("reference note", self.changeTempoReference),
|
|
]
|
|
if not self.staticExportItem["positionInBlock"] == 0:
|
|
listOfLabelsAndFunctions.append(("delete", lambda: api.removeTempoItem(self.staticExportItem["id"])))
|
|
|
|
for text, function in listOfLabelsAndFunctions:
|
|
if text == "separator":
|
|
self.addSeparator()
|
|
else:
|
|
a = QtWidgets.QAction(text, menu)
|
|
menu.addAction(a)
|
|
a.triggered.connect(function)
|
|
|
|
pos = QtGui.QCursor.pos()
|
|
pos.setY(pos.y() + 5)
|
|
self.ungrabMouse() #if we don't ungrab and the user clicks the context menu "away" by clicking in an empty area this will still get registered as mouseclick belonging to the current item
|
|
menu.exec_(pos)
|
|
|
|
def mouseDoubleClickEvent(self, event):
|
|
self.changeTempoUnits()
|
|
|
|
def changeInterpolationType(self):
|
|
li = api.getListOfGraphInterpolationTypesAsStrings()
|
|
graphTypeString = QtWidgets.QInputDialog.getItem(self.scene().parentView, "Interpolation Type", "choose Interpolation Type", li, li.index(self.staticExportItem["graphType"]), False) #list and default value from that list
|
|
if graphTypeString[1]: #[1] bool if canceled
|
|
api.changeTempoItemInterpolation(self.staticExportItem["id"], graphTypeString[0])
|
|
|
|
def changeTempoUnits(self):
|
|
#TODO: unify changeTempoUnits and changeTempoReference
|
|
self.parentTempoTrack.lastTouchedReferenceTicks = self.staticExportItem["referenceTicks"]
|
|
unitsPerMinute = self.staticExportItem["unitsPerMinute"]
|
|
referenceTicks = self.staticExportItem["referenceTicks"]
|
|
ccValue = QtWidgets.QInputDialog.getInt(self.scene().parentView, "Units per Minute", "how many reference notes per minute?", value=unitsPerMinute, min=20, max=999)
|
|
if ccValue[1]: #[1] bool if canceled
|
|
moveInTicks = self.getXDifferenceAsBackendValue()
|
|
unitsPerMinute = ccValue[0]
|
|
api.changeTempoItem(self.staticExportItem["id"], moveInTicks, unitsPerMinute, referenceTicks)
|
|
|
|
def changeTempoReference(self):
|
|
#TODO: unify changeTempoUnits and changeTempoReference
|
|
self.parentTempoTrack.lastTouchedReferenceTicks = self.staticExportItem["referenceTicks"]
|
|
unitsPerMinute = self.staticExportItem["unitsPerMinute"]
|
|
referenceTicks = self.staticExportItem["referenceTicks"]
|
|
rhythmString = QtWidgets.QInputDialog.getItem(self.scene().parentView, "Reference Note", "What is the reference note for the tempo?", constantsAndConfigs.prettyExtendedRhythmsStrings, constantsAndConfigs.prettyExtendedRhythmsValues.index(referenceTicks), False)
|
|
if rhythmString[1]: #bool. Canceled?
|
|
for k, v in constantsAndConfigs.prettyExtendedRhythms:
|
|
if v == rhythmString[0]:
|
|
moveInTicks = self.getXDifferenceAsBackendValue()
|
|
referenceTicks = k
|
|
self.parentTempoTrack.lastTouchedReferenceTicks = k
|
|
api.changeTempoItem(self.staticExportItem["id"], moveInTicks, unitsPerMinute, referenceTicks)
|
|
|
|
|
|
class TempoBlockEndMarker(QtWidgets.QGraphicsLineItem):
|
|
def __init__(self, parentCCPath, staticExportItem):
|
|
#super().__init__(0,-1*parentCCPath.rect().height(),0, parentCCPath.rect().height()) #x1, y1, x2, y2
|
|
super().__init__(0,-1, 0, parentCCPath.rect().height() + 4) #x1, y1, x2, y2
|
|
assert not self.line().isNull() #needed to do self.line().setLength(x)
|
|
self.parentCCPath = parentCCPath
|
|
self.staticExportItem = staticExportItem
|
|
self.setFlags(QtWidgets.QGraphicsItem.ItemIsMovable|QtWidgets.QGraphicsItem.ItemSendsGeometryChanges|QtWidgets.QGraphicsItem.ItemIsFocusable|QtWidgets.QGraphicsItem.ItemClipsToShape)
|
|
self.setAcceptHoverEvents(True)
|
|
self.setCursor(QtCore.Qt.SizeHorCursor)
|
|
#self.setBrush(QtGui.QColor("red"))
|
|
pen = QtGui.QPen() # creates a default pen
|
|
if not self.staticExportItem["exportsAllItems"]:
|
|
pen.setStyle(QtCore.Qt.DotLine)
|
|
pen.setWidth(2)
|
|
self.setPen(pen)
|
|
|
|
self.inactivePen = pen
|
|
self.inactivePen.setColor(QtGui.QColor("black"))
|
|
|
|
self.activePen = QtGui.QPen(pen)
|
|
self.activePen.setColor(QtGui.QColor("cyan"))
|
|
|
|
def shape(self):
|
|
"""Return a more accurate shape for this item so that
|
|
mouse hovering is more accurate"""
|
|
path = QtGui.QPainterPath()
|
|
path.addRect(QtCore.QRectF(-2, -2, 4, self.parentCCPath.rect().height() + 4 )) #this is directly related to inits parameter x, y, w, h
|
|
return path
|
|
|
|
def hoverEnterEvent(self, event):
|
|
self.setPen(self.activePen)
|
|
|
|
def hoverLeaveEvent(self, event):
|
|
self.setPen(self.inactivePen)
|
|
|
|
def allItemsRightOfMe(self):
|
|
for item in self.parentCCPath.items:
|
|
if item.x()+7 >= self.x():
|
|
yield item
|
|
|
|
def itemsLeftAndRightOfMe(self):
|
|
left = []
|
|
right = []
|
|
for item in self.parentCCPath.items:
|
|
if item.x()+7 >= self.x():
|
|
right.append(item)
|
|
else:
|
|
left.append(item)
|
|
return left, right
|
|
|
|
def mousePressEvent(self, event):
|
|
super().mousePressEvent(event)
|
|
self.lastPos = self.pos()
|
|
self.itemsLeft, self.itemsRight = self.itemsLeftAndRightOfMe()
|
|
|
|
#if event.button() == 1: # QtCore.Qt.MouseButton.LeftButton
|
|
# for i in self.allItemsRightOfMe():
|
|
# i.hide()
|
|
# self.parentCCPath.appendGraphBlockButtonProxy.hide()
|
|
|
|
def mouseMoveEvent(self, event):
|
|
"""Only active when the item is also selected and left
|
|
mouse button is down. Not any mouse event, no hover.
|
|
|
|
This does not actually change the backend items. The change is done on mouseRelease"""
|
|
super().mouseMoveEvent(event)
|
|
self.setY(self.lastPos.y())
|
|
|
|
delta = event.scenePos() - event.lastScenePos()
|
|
deltaX = delta.x()
|
|
|
|
endingRelativeToBlockStart = self.x() * constantsAndConfigs.ticksToPixelRatio - self.staticExportItem["position"]
|
|
|
|
if deltaX < 0 and endingRelativeToBlockStart < api.D1: #some musical sanity. Also prevents that the block gets a negative duration and the backends throws an exception
|
|
event.accept()
|
|
return None
|
|
|
|
for item in self.itemsRight:
|
|
item.moveBy(deltaX, 0)
|
|
for item in self.itemsLeft:
|
|
if item.x() + 15 >= self.x():
|
|
item.hide()
|
|
|
|
#self.parentCCPath.appendGraphBlockButtonProxy.moveBy(deltaX, 0)
|
|
self.parentCCPath.appendGraphBlockButtonProxy.hide()
|
|
|
|
def mouseReleaseEvent(self, event):
|
|
"""After moving a point around
|
|
send an update to the backend"""
|
|
super().mouseReleaseEvent(event)
|
|
if event.button() == 1: # QtCore.Qt.MouseButton.LeftButton
|
|
self.parentCCPath.appendGraphBlockButtonProxy.show()
|
|
endingRelativeToBlockStart = self.x() * constantsAndConfigs.ticksToPixelRatio - self.staticExportItem["position"]
|
|
if constantsAndConfigs.snapToGrid:
|
|
endingRelativeToBlockStart = round(endingRelativeToBlockStart / constantsAndConfigs.gridRhythm) * constantsAndConfigs.gridRhythm
|
|
|
|
assert endingRelativeToBlockStart > 0
|
|
api.changeTempoBlockDuration(self.staticExportItem["id"], endingRelativeToBlockStart)
|
|
|
|
def deactivate_contextMenuEvent(self, event):
|
|
menu = QtWidgets.QMenu()
|
|
|
|
listOfLabelsAndFunctions = [
|
|
("restore to full duration ", self.contextRestore),
|
|
]
|
|
|
|
for text, function in listOfLabelsAndFunctions:
|
|
if text == "separator":
|
|
self.addSeparator()
|
|
else:
|
|
a = QtWidgets.QAction(text, menu)
|
|
menu.addAction(a)
|
|
a.triggered.connect(function)
|
|
|
|
pos = QtGui.QCursor.pos()
|
|
pos.setY(pos.y() + 5)
|
|
self.ungrabMouse() #if we don't ungrab and the user clicks the context menu "away" by clicking in an empty area this will still get registered as mouseclick belonging to the current item
|
|
menu.exec_(pos)
|
|
|
|
def contextRestore(self):
|
|
print (self.staticExportItem)
|
|
#api.changeTempoBlockDuration(self.staticExportItem["id"], endingRelativeToBlockStart)
|
|
|