#! /usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Copyright 2021, Nils Hilbricht, Germany ( https://www.hilbricht.net )

This file is part of the Laborejo Software Suite ( https://www.laborejo.org ),

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 <http://www.gnu.org/licenses/>.
"""

import logging; logger = logging.getLogger(__name__); logger.info("import")
from PyQt5 import QtCore, QtGui, QtSvg, QtWidgets
from .constantsAndConfigs import constantsAndConfigs
from template.qtgui.helper import stringToColor, removeInstancesFromScene, callContextMenu, stretchLine, stretchRect
import engine.api as api

class CCPath(QtWidgets.QGraphicsRectItem):
    """
    A CCPath only exists when the backend track has a cc-part activated.
    Since each track has 128 potential CC paths we don't create and
    save a bunch of empty ones, neither in the backend nor here.

    Therefore: you cannot display a message "click here to create
    a CC track/block" here in the CC Path since the CCPath does not
    exist at this point in time. Thus this button is created in the track. One button for all CCs.

    There are two item types. Movable, or handle-points,
    and non-movable: interpolation-points.

    The backend only knows handle points and a function which
    interpolates until the next point.

    However, that results only in CC values 0-128 anyway. So
    we use these directly.

    We don't lie to the user. There is no smooth curve, not even
    a linear one, between CC values 65 and 66. It is just a step.

    So we show the steps."""

    def __init__(self, parentGuiTrack, parentDataTrackId):
        super().__init__(0, 0, 0, 0)
        self.setAcceptHoverEvents(True)
        #self.setFlags(QtWidgets.QGraphicsItem.ItemIsMovable|QtWidgets.QGraphicsItem.ItemSendsGeometryChanges|QtWidgets.QGraphicsItem.ItemIsFocusable|QtWidgets.QGraphicsItem.ItemIgnoresParentOpacity)
        self.setFlags(QtWidgets.QGraphicsItem.ItemIsFocusable|QtWidgets.QGraphicsItem.ItemIgnoresParentOpacity)
        #self.setBrush(QtCore.Qt.red) #debug
        self.setPen(QtGui.QPen(QtCore.Qt.transparent))

        self.parentDataTrackId = parentDataTrackId
        self.parentGuiTrack = parentGuiTrack
        self.userItems = []
        self.interpolatedItems = []
        self.other = []
        self.blockListCache = [] #backend dictExport items. updated through self.updateGraphBlockTrack
        self.blockdictCache = {} #blockId:guiBlock  updated through self.updateGraphBlockTrack
        self.transparentBlockHandles = [] #transparentBlockHandles in correct order. updated through self.updateGraphBlockTrack

    def itemChange(self, changeEnum, value):
        if changeEnum == QtWidgets.QGraphicsItem.ItemVisibleHasChanged: #12
            if self.isVisible():
                self.parentGuiTrack.universalCreateFirstCCBlock.hide()
            else:
                self.parentGuiTrack.universalCreateFirstCCBlock.show()

        return super().itemChange(changeEnum, value)

    def mousePressEvent(self, event):
        if event.button() == 1: #QtCore.Qt.LeftButton
            self.add(event.pos())

    @property
    def items(self):
        return self.userItems + self.interpolatedItems + self.other

    #@property
    #def transparentBlockHandles(self):
        #return CCGraphTransparentBlock.instances This does not work because we need the blocks per track, not all of them in all tracks.

    def createGraphicItemsFromData(self, staticRepresentationList):
        """The tick position is, as always, a long tickvalue from the
        backend.
        The CC value, 0-127, has its default 0-line on the middle line
        as all items, since this is the track root. Since CC 64 is the
        middle value for CCs we shift all the points down by 64 so
        the middle staff line becomes CC 64.
        """
        for item in self.items:
            self.scene().removeWhenIdle(item)
        self.other = []
        self.userItems = []
        self.interpolatedItems = []

        for point in staticRepresentationList:
            if point["type"] == "interpolated":
                p = CCInterpolatedPoint()
                self.interpolatedItems.append(p)
                self.userItems[-1].interpolatedItemsRight.append(p)
            else: #user, lastInBlock or lastInTrack
                p = CCUserPoint(self, point)

                if self.userItems:
                    p.interpolatedItemsLeft = self.userItems[-1].interpolatedItemsRight

                self.userItems.append(p)

                t = QtWidgets.QGraphicsSimpleTextItem(str(abs(point["value"])))
                #t.setFont(constantsAndConfigs.theFont)
                t.setParentItem(self)
                t.setScale(0.75)
                self.other.append(t)
                t.setPos(point["position"] / constantsAndConfigs.ticksToPixelRatio, 29)

            p.setParentItem(self)
            p.setPos(point["position"] / constantsAndConfigs.ticksToPixelRatio , (point["value"]+64)*0.4375) #value goes from -0 to -127 and are compressed to fit between two lines in the staff.


    def updateGraphBlockTrack(self, staticRepresentationList):
        """Handles and visualizes block boundaries"""
        #{"type" : "GraphBlock", "id":id(block), "name":block.name, "duration":block.duration, "position":tickCounter}

        self.blockListCache = staticRepresentationList #sorted list
        self.blockdictCache = {} #reset

        for tbh in self.transparentBlockHandles:
            tbh.setParentItem(None)
            tbh.scene().removeWhenIdle(tbh)
        self.transparentBlockHandles = [] #reset

        #removeInstancesFromScene(CCGraphTransparentBlock) #this removes ALL blocks in all tracks from the scene. don't.
        for dictExportItem in staticRepresentationList:
            guiBlock = CCGraphTransparentBlock(parent = self, staticExportItem = dictExportItem, x = 0, y = -28, w = dictExportItem["duration"] / constantsAndConfigs.ticksToPixelRatio, h = 2*28)
            guiBlock.setParentItem(self)
            guiBlock.setPos(dictExportItem["position"] / constantsAndConfigs.ticksToPixelRatio,0)
            self.blockdictCache[dictExportItem["id"]] = guiBlock
            self.transparentBlockHandles.append(guiBlock)

        #Use the last item of the loop above to draw length of the boundary lines.
        maximumPixels = (dictExportItem["duration"] + dictExportItem["position"]) / constantsAndConfigs.ticksToPixelRatio
        upperLine = QtWidgets.QGraphicsLineItem(0,-28,maximumPixels,-28) #x1, y1, x2, y2
        #upperLine.setPen(QtGui.QColor("red"))
        upperLine.setParentItem(self)
        self.other.append(upperLine)
        lowerLine = QtWidgets.QGraphicsLineItem(0,28,maximumPixels,28) #x1, y1, x2, y2
        #lowerLine.setPen(QtGui.QColor("green"))
        lowerLine.setParentItem(self)
        self.other.append(lowerLine)
        upperLine.setOpacity(0.3)
        lowerLine.setOpacity(0.3)
        upperLine.setPos(0,0)
        lowerLine.setPos(0,0)
        #for self.stretchXCoordinates
        self.upperLine = upperLine
        self.lowerLine = lowerLine

        self.setRect(0,-28,maximumPixels,2*28)

    def stretchXCoordinates(self, factor):
        """Reposition the items on the X axis.
        Call goes through all parents/children, starting from ScoreView._stretchXCoordinates.
        Docstring there."""
        stretchRect(self, factor)
        stretchLine(self.upperLine, factor)
        stretchLine(self.lowerLine, factor)

        for item in self.items:
            item.setX(item.pos().x() * factor)

        for transparentBlockHandle in self.transparentBlockHandles:
            transparentBlockHandle.setX(transparentBlockHandle.pos().x() * factor)
            transparentBlockHandle.stretchXCoordinates(factor)

    def blocKByPosition(self, tickPositionAbsoluteBackendValue):
        for block in self.blockListCache:
            start =  block["position"]
            end = start + block["duration"]
            if start <= tickPositionAbsoluteBackendValue < end:
                return block["id"], block["position"]
        return None #After the last block.

    def blockAt(self, qScenePosition):
        """For compatibility with track.blockAt
        Can return a block or None for "after the last block" """
        backendPosition = qScenePosition * constantsAndConfigs.ticksToPixelRatio
        result = self.blocKByPosition(backendPosition)
        if result:
            blockId, blockBackendPosition = result
            assert blockId in self.blockdictCache
            return self.blockdictCache[blockId]
        else:
            return None

    def getCCNumber(self):
        """The CC number is a dynamic value"""
        for ccNumber, ccGraphTrack in self.parentGuiTrack.ccPaths.items():
            if ccGraphTrack is self:
                return ccNumber
        else:
            raise ValueError("This Block is not in the ccPath dict of the GuiTrack""")


    def getYAsBackendValue(self, y):
        newCCValue = int(y / 0.4375 - 64) #value goes from -0 to -127 and are compressed to fit between two lines in the staff.
        newCCValue = abs(newCCValue)
        assert 0 <= newCCValue < 128
        return newCCValue

    def add(self, qPos):
        """Only activated through the hover area which gives are the
        most control over where clicks are allowed.
        Also there was a weird bug that the CCPath itself cannot
        detect mouse clicks in the right position"""
        y = qPos.y()
        if -28 < y < 28:
            sp = qPos.x() * constantsAndConfigs.ticksToPixelRatio
            blockId, blockStartOffset = self.blocKByPosition(sp) #get the block before rounding. Otherwise rounding might result in a position higher than the blocks duration
            if constantsAndConfigs.snapToGrid:
                sp = round(sp / constantsAndConfigs.gridRhythm) * constantsAndConfigs.gridRhythm
            positionInBlockBackendTicks = sp - blockStartOffset
            api.addGraphItem(blockId, positionInBlockBackendTicks, self.getYAsBackendValue(y))

class CCGraphTransparentBlock(QtWidgets.QGraphicsRectItem):
    instances = []

    def __init__(self, parent, staticExportItem, x, y, w, h):
        self.__class__.instances.append(self)
        super().__init__(x, y, w, h)
        #self.setFlags(QtWidgets.QGraphicsItem.ItemDoesntPropagateOpacityToChildren|QtWidgets.QGraphicsItem.ItemIsMovable) #no mouseReleaseEvent without selection or movable.
        self.setFlags(QtWidgets.QGraphicsItem.ItemDoesntPropagateOpacityToChildren) #no mouseReleaseEvent without selection or movable.

        self.parentCCPath = parent
        self.parent = parent #for compatibility with block movement to the appending positon.
        self.parentGuiTrack = parent.parentGuiTrack  #redundant, but specifically for block movement. see ScoreScene

        self.color = stringToColor(staticExportItem["name"])
        self.setBrush(self.color)
        self.trans = QtGui.QColor("transparent")
        self.setPen(self.trans)
        self.setBrush(self.trans)
        self.setOpacity(0.9) #only visible during drag and move
        self.staticExportItem = staticExportItem

        self.blockEndMarker = CCGraphBlockEndMarker(self, staticExportItem)
        self.blockEndMarker.setParentItem(self)
        self.blockEndMarker.setPos(staticExportItem["duration"]  / constantsAndConfigs.ticksToPixelRatio, 0)

        self.posBeforeMove = None
        self.cursorPosOnMoveStart = None

    def stretchXCoordinates(self, factor):
        """Reposition the items on the X axis.
        Call goes through all parents/children, starting from ScoreView._stretchXCoordinates.
        Docstring there."""
        stretchRect(self, factor)
        self.blockEndMarker.setX(self.blockEndMarker.pos().x() * factor)

    def mousePressEventCustom(self, event):
        """Custom gets called by the scene mouse press event directly only when the right keys
        are hold down.
        e.g. shift + middle mouse button for moving this block"""
        self.posBeforeMove = self.pos()
        self.cursorPosOnMoveStart = QtGui.QCursor.pos()
        self.setBrush(self.color)
        super().mousePressEvent(event)

    def mouseMoveEventCustom(self, event):
        """Custom gets called by the scene mouse press event directly only when the right keys
        are hold down
        e.g. shift + middle mouse button for moving this block"""
        # All the positions below don't work. They work fine when dragging Tracks around but not this Item. I can't be bothered to figure out why.
        #scenePos() results ins an item position that is translated down and right. The higher the x/y value the more the offset
        #Instead we calculate our delta ourselves.

        #self.setPos(self.mapToItem(self, event.scenePos()))
        #self.setPos(self.mapFromScene(event.scenePos()))
        #posGlobal = QtGui.QCursor.pos()
        #posView = self.parentCCPath.parentScore.parentView.mapFromGlobal(posGlobal) #a widget
        #posScene = self.parentCCPath.parentScore.parentView.mapToScene(posView)
        #print (posGlobal, posView, posScene, event.scenePos())


        if self.cursorPosOnMoveStart:
            self.setPos(event.scenePos())

        """
        #does not work with zooming
        if self.cursorPosOnMoveStart:
            delta = QtGui.QCursor.pos() - self.cursorPosOnMoveStart
            new = self.posBeforeMove + delta
            if new.x() < 0:
                #self.setPos(0, self.posBeforeMove.y())
                self.setPos(0, new.y())
            else:
                #self.setPos(new.x(), self.posBeforeMove.y()) #only move in one track
                self.setPos(new.x(), new.y())
            #event.ignore() #this blocks the qt movable object since we already move the object on our own.
        """
        super().mouseMoveEvent(event)


    def mouseReleaseEventCustom(self, event):
        """Custom gets called by the scene mouse press event directly only when the right keys
        are hold down"""
        self.setBrush(self.trans)
        self.setPos(self.posBeforeMove) #In case the block was moved to a position where no track is (below the tracks) we just reset the graphics. If the moving was correct then the new position will be set by redrawing the whole Conductor.
        self.posBeforeMove = None
        self.cursorPosOnMoveStart = None
        super().mouseReleaseEvent(event)

    def splitHere(self, event):
        posRelativeToBlockStart = event.scenePos().x() * constantsAndConfigs.ticksToPixelRatio - self.x() * constantsAndConfigs.ticksToPixelRatio
        if constantsAndConfigs.snapToGrid:
            posRelativeToBlockStart = round(posRelativeToBlockStart / constantsAndConfigs.gridRhythm) * constantsAndConfigs.gridRhythm

        if posRelativeToBlockStart > 0:
            api.splitCCBlock(self.staticExportItem["id"], int(posRelativeToBlockStart))

    def contextMenuEvent(self, event):
        listOfLabelsAndFunctions = [
            ("split here", lambda: self.splitHere(event)),
            ("duplicate", lambda: api.duplicateCCBlock(self.staticExportItem["id"])),
            ("create content link", lambda: api.duplicateContentLinkCCBlock(self.staticExportItem["id"])),
            ("unlink", lambda: api.unlinkCCBlock(self.staticExportItem["id"])),
            ("separator", None),
            ("join with next block", lambda: api.mergeWithNextGraphBlock(self.staticExportItem["id"])),
            ("delete block", lambda: api.deleteCCBlock(self.staticExportItem["id"])),
            ("separator", None),
            ("append block at the end", lambda ccNum = self.parentCCPath.getCCNumber(): api.appendGraphBlock(self.parentCCPath.parentDataTrackId, ccNum)),
            ]
        callContextMenu(listOfLabelsAndFunctions)

class CCGraphBlockEndMarker(QtWidgets.QGraphicsLineItem):
    def __init__(self, parentTransparentBlock, staticExportItem):
        super(CCGraphBlockEndMarker, self).__init__(0,-28,0,28) #x, y, w, h
        self.parentTransparentBlock = parentTransparentBlock
        self.parentCCPath = self.parentTransparentBlock.parent
        self.staticExportItem = staticExportItem
        self.setFlags(QtWidgets.QGraphicsItem.ItemIsMovable|QtWidgets.QGraphicsItem.ItemSendsGeometryChanges|QtWidgets.QGraphicsItem.ItemIsFocusable)
        self.setAcceptHoverEvents(True)
        self.setCursor(QtCore.Qt.SizeHorCursor)
        self.lastPos = self.pos()
        #self.setBrush(QtGui.QColor("red"))
        pen = QtGui.QPen()  # creates a default pen
        if not self.staticExportItem["exportsAllItems"]:
            pen.setStyle(QtCore.Qt.DashDotLine)
        pen.setWidth(2)
        self.setPen(pen)
        self.setZValue(10)

        self.inactivePen = pen
        self.inactivePen.setColor(QtGui.QColor("black"))

        self.activePen = QtGui.QPen(pen)
        self.activePen.setColor(QtGui.QColor("cyan"))

    def allItemsRightOfMe(self):
        for item in self.parentCCPath.items:
            if item.x() > self.x():
                yield item

    def shape(self):
        """Qt Function
        Return a more accurate shape for this item so that
        mouse hovering is more accurate"""
        path = QtGui.QPainterPath()
        path.addRect(QtCore.QRectF(-3, -1/2 * constantsAndConfigs.trackHeight, 9, constantsAndConfigs.trackHeight)) #this is directly related to inits parameter x, y, w, h
        return path

    def mouseReleaseEvent(self, event):
        """After moving a point around
        send an update to the backend"""
        super(CCGraphBlockEndMarker, self).mouseReleaseEvent(event)
        if event.button() == 1: #QtCore.Qt.LeftButton
            x = event.scenePos().x()
            #x = self.x()
            x = x * constantsAndConfigs.ticksToPixelRatio
            endingRelativeToBlockStart = x - self.staticExportItem["position"]
            if constantsAndConfigs.snapToGrid:
                endingRelativeToBlockStart = round(endingRelativeToBlockStart / constantsAndConfigs.gridRhythm) * constantsAndConfigs.gridRhythm
            assert endingRelativeToBlockStart > 0
            api.changeGraphBlockDuration(self.staticExportItem["id"], endingRelativeToBlockStart)


    def mousePressEvent(self, event):
        super(CCGraphBlockEndMarker, self).mousePressEvent(event)

        if event.button() == 1: #QtCore.Qt.LeftButton
            for i in self.allItemsRightOfMe():
                i.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."""
        super(CCGraphBlockEndMarker, self).mouseMoveEvent(event)
        self.setY(self.lastPos.y())

    def hoverEnterEvent(self, event):
        #self.setZValue(20)
        #self.parentTransparentBlock.setZValue(19)
        self.setPen(self.activePen)
        self.parentTransparentBlock.setBrush(self.parentTransparentBlock.color)

    def hoverLeaveEvent(self, event):
        #self.setZValue(10)
        #self.parentTransparentBlock.setZValue(9)
        self.setPen(self.inactivePen)
        self.parentTransparentBlock.setBrush(self.parentTransparentBlock.trans)


class CCInterpolatedPoint(QtWidgets.QGraphicsEllipseItem):
    """This is practically read-only"""
    def __init__(self):
        super(CCInterpolatedPoint, self).__init__(0, 0,1,1) #x,y,w,h
        self.setOpacity(0.6)
        self.setBrush(QtCore.Qt.black) #fill
        self.setEnabled(False)
        self.setZValue(1)

class CCUserPoint(QtWidgets.QGraphicsEllipseItem):
    """the position is set by the parent"""
    def __init__(self, parentCCPath, staticExportItem):
        super(CCUserPoint, self).__init__(-3,-3,6,6) #x,y,w,h
        #color = QtGui.QColor()
        #color.setHsl(127-abs(value), 255, 127)  #l(h, s, l) # 8bit values. Sets a HSL color value; h is the hue, s is the saturation, l is the lightness. l goes from black(0), over color(255/2) to white (255). #0 hue is green, 128 is red
        #self.setBrush(color) #fill
        self.setBrush(QtGui.QColor("cyan"))
        self.setFlags(QtWidgets.QGraphicsItem.ItemIsMovable|QtWidgets.QGraphicsItem.ItemSendsGeometryChanges|QtWidgets.QGraphicsItem.ItemIsFocusable)
        self.setAcceptHoverEvents(True)
        self.setCursor(QtCore.Qt.SizeAllCursor)
        self.parentCCPath = parentCCPath
        self.staticExportItem = staticExportItem
        self.interpolatedItemsRight = []
        self.interpolatedItemsLeft = []
        self.setZValue(9)

    def shape(self):
        """Return a more accurate shape for this item so that
        mouse hovering is more accurate"""
        path = QtGui.QPainterPath()
        path.addEllipse(QtCore.QRectF(-5, -5, 14, 14)) #this is directly related to inits parameter. -3, -3, 6, 6.
        return path

    def mousePressEvent(self, event):
        super().mousePressEvent(event)
        self.lastPos = self.pos()

        if event.button() == 1: #QtCore.Qt.LeftButton
            self.setCursor(QtCore.Qt.BlankCursor)
            for i in self.interpolatedItemsLeft + self.interpolatedItemsRight:
                i.hide()

    def mouseDoubleClickEvent(self, event):
        self.changeCCValue()

    def hoverEnterEvent(self, event):
        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.
        Needs grabKeyboard, but NOT setFocus"""
        key = event.key()
        if key == 16777223:
            self.delete()
        else:
            return super().keyPressEvent(event)

    def mouseReleaseEvent(self, event):
        """After moving a point around
        send an update to the backend"""
        super(CCUserPoint, self).mouseReleaseEvent(event)
        self.setCursor(QtCore.Qt.SizeAllCursor)
        if event.button() == 1: #QtCore.Qt.LeftButton
            api.changeGraphItem(self.staticExportItem["id"], self.getXDifferenceAsBackendValue(), self.getYAsBackendValue()) #send update to the backend, don't wait for callback.

    def mouseMoveEvent(self, event):
        """Only active when the item is also selected and left
        mouse button is down. Not any mouse event, no hover."""
        modifiers = QtWidgets.QApplication.keyboardModifiers()
        super(CCUserPoint, self).mouseMoveEvent(event)
        if modifiers == QtCore.Qt.ShiftModifier:
            self.setY(self.lastPos.y())
        elif modifiers == QtCore.Qt.AltModifier:
            self.setX(self.lastPos.x())

        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)
            #Make mouse cursor movement not weird.
            a = self.scene().parentView.mapFromScene(self.pos())
            b = self.scene().parentView.viewport().mapToGlobal(a)
            QtGui.QCursor.setPos(b.x(), QtGui.QCursor.pos().y())

        withinYRange = -28 <= self.y() <= 28
        if not withinYRange:
            if self.y() < -28:
                self.setY(-27.9)
            else:
                self.setY(28)
            #Make mouse cursor movement not weird.
            a = self.scene().parentView.mapFromScene(self.pos())
            b = self.scene().parentView.viewport().mapToGlobal(a)
            QtGui.QCursor.setPos(QtGui.QCursor.pos().x(), b.y())

    def delete(self):
        """Instruct the backend to delete this item. Will trigger a
        callback to redraw the graphTrack"""
        api.removeGraphItem(self.staticExportItem["id"])

    def contextMenuEvent(self, event):
        menu = QtWidgets.QMenu()

        listOfLabelsAndFunctions = [
            ("interpolation Type", self.changeInterpolationType),
            ("CC Value", self.changeCCValue),
            ("delete", self.delete), #deletes the last hover item which is of course the current one.
            ]

        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 and change the value to some insane amount, breaking the point.
        menu.exec_(pos)

    def changeInterpolationType(self):
        graphTypeString = QtWidgets.QInputDialog.getItem(self.scene().parentView, "Interpolation Type", "choose Interpolation Type", api.getListOfGraphInterpolationTypesAsStrings(), 0, False)
        if graphTypeString[1]: #[1] bool if canceled
            api.changeGraphItemInterpolation(self.staticExportItem["id"], graphTypeString[0])

    def changeCCValue(self):
        ccValue = QtWidgets.QInputDialog.getInt(self.scene().parentView, "CC Value", "specify midi Continuous Control value", value=64, minValue=0, maxValue=127)
        if ccValue[1]: #[1] bool if canceled
            api.changeGraphItem(self.staticExportItem["id"], self.getXDifferenceAsBackendValue(), ccValue[0]) #send update to the backend, don't wait for callback.

    def getYAsBackendValue(self):
        newCCValue = int(self.y() / 0.4375 - 64) #value goes from -0 to -127 and are compressed to fit between two lines in the staff.
        newCCValue = abs(newCCValue)
        assert 0 <= newCCValue < 128
        return newCCValue

    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
