#! /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 itertools import groupby
from engine.items import Chord, KeySignature
from .constantsAndConfigs import constantsAndConfigs
from template.qtgui.helper import stretchRect
import engine.api as api


from template.engine import pitch


"""All svg items (execept pitch-related) graphics are expected to be
already on the correct position and will be inserter ON the middle line
That means g-clef needs to be 14 pxiels below the y=0 coordinate since
these are two stafflines. Or in other words: Open the graphic and
scale and reposition it until it fits correctly when inserted with
setPos(0,0)"""

#Atomic items:

class GuiTieCurveGraphicsItem(QtWidgets.QGraphicsPathItem):
    pen = QtGui.QPen()
    pen.setCapStyle(QtCore.Qt.RoundCap)
    pen.setWidth(2)

    def __init__(self, noteExportObject):
        """The origin of this item is the scene position.
        We create a curve by drawing a straight line from zero
        to end (the local item and the receiving tie item) and then
        bend it down in the middle, leaving one of the control points
        empty (0,0)."""
        super().__init__()
        self.setPen(GuiTieCurveGraphicsItem.pen)
        self.setTransform(QtGui.QTransform.fromTranslate(0, -6), True)
        self.noteExportObject = noteExportObject
        self.draw()

    def draw(self):
        lengthInPixel = self.noteExportObject["tieDistanceInTicks"] / constantsAndConfigs.ticksToPixelRatio
        path = QtGui.QPainterPath()
        path.cubicTo(0, 0, lengthInPixel/2, constantsAndConfigs.stafflineGap, lengthInPixel, 0) # ctrlPt1x, ctrlPt1y, ctrlPt2x, ctrlPt2y, endPtx, endPty
        self.setPath(path) #set path edits the old one in place

    def stretchXCoordinates(self, factor):
        """Called directly by the track by iterating through instances"""
        self.draw()


class GuiPositionMarker(QtWidgets.QGraphicsRectItem):
    def __init__(self, height, position = 1):
        """A simple position marker that connects an annotation above a staff with the staff.
        Height and position are calculated in stafflines/gaps"""
        super().__init__(-2, position*constantsAndConfigs.stafflineGap, 1, height * constantsAndConfigs.stafflineGap) #x,y, w, h
        self.setBrush(QtCore.Qt.black)

class GuiTupletNumber(QtWidgets.QGraphicsItem):
    """The baseline is the scene Position.
    We position in the up direction"""

    pen = QtGui.QPen()
    pen.setColor(QtGui.QColor("darkGrey"))
    pen.setWidth(1)

    def boundingRect(self):
        return self.childrenBoundingRect()

    def __init__(self, upper, lower):
        super().__init__()
        self.setFlag(QtWidgets.QGraphicsItem.ItemHasNoContents, True) #only child items. Without this we get notImplementedError: QGraphicsItem.paint() is abstract and must be overridden

        #self.upper = QtWidgets.QGraphicsSimpleTextItem(str(upper))
        #self.divider = QtWidgets.QGraphicsLineItem(0,0,10,0)
        #self.divider.rotate(-70)
        #self.divider.setPen(GuiTupletNumber.pen)
        self.lower = QtWidgets.QGraphicsSimpleTextItem(str(lower))

        #self.upper.setParentItem(self)
        #self.divider.setParentItem(self)
        self.lower.setParentItem(self)

        #self.upper.setPos(-1,-8)
        #self.divider.setPos(5,5)
        #self.lower.setPos(7,-5)

        self.lower.setPos(-1,-4)
        self.setScale(0.75)

class GuiItem(QtWidgets.QGraphicsItem):
    """A blank item. Subclass to implement your own"""
    def __init__(self, staticItem):
        super().__init__()
        self.setFlag(QtWidgets.QGraphicsItem.ItemHasNoContents, True) #only child items. Without this we get notImplementedError: QGraphicsItem.paint() is abstract and must be overridden
        self.staticItem = staticItem
        dur = self.staticItem["completeDuration"]
        exceptions = ["BlockEndMarker"]
        #Deactivated after stretchX was reimplemented properly as recursive function and not a catch all on all items. I don't think that was used anywhere else. #self.rectangles = [] #Prevents typechecking. only implemented in GuiChord

        self.pixelPosition = self.staticItem["tickindex"] / constantsAndConfigs.ticksToPixelRatio
        self.pixelWidth = self.staticItem["completeDuration"] / constantsAndConfigs.ticksToPixelRatio

        #if dur or self.staticItem["type"] in exceptions:
        #    self.pixelPosition = self.staticItem["tickindex"] / constantsAndConfigs.ticksToPixelRatio
        #    self.pixelWidth = self.staticItem["completeDuration"] / constantsAndConfigs.ticksToPixelRatio
        #else:
        #    self.pixelPosition = self.staticItem["tickindex"] / constantsAndConfigs.ticksToPixelRatio +  constantsAndConfigs.negativeMagicPixel*6 #an offset to the left. this will still get in the way for short notes, but for now it is better than colliding 100% when placed on the note. #TODO: place above the track
        #    self.pixelWidth = 25 #Does not need to be the actual size because this is ONLY important when the item gets inserted at the appending position and the cursor needs to jump "over" the current item, because there is no next one. This is the direct response to the cursor method that sets the appending position as items tickposition+pixelwidth.

    def updateVisibility(self):
        """Prevent against typechecking. This method is only implemented
        in GuiChord"""
        pass


    def boundingRect(self):
        return self.childrenBoundingRect()

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

class GuiRectangleNotehead(QtWidgets.QGraphicsRectItem):
    """Displays the pitch and actual duration of one note when in rectangle view mode"""

    def __init__(self, parent, noteExportObject):
        #Dimensions and Position
        x = noteExportObject["leftModInTicks"] / constantsAndConfigs.ticksToPixelRatio
        y = -1 * constantsAndConfigs.stafflineGap / 2 + 1
        w = (noteExportObject["rightModInTicks"] + noteExportObject["completeDuration"] - noteExportObject["leftModInTicks"]) / constantsAndConfigs.ticksToPixelRatio
        h = constantsAndConfigs.stafflineGap #- 2  #2 pixel to make room for the pen-width.
        super().__init__(x, y, w, h)
        self.setPos(0, constantsAndConfigs.stafflineGap * noteExportObject["dotOnLine"] / 2)
        self.setParentItem(parent)

        #Prepare self.shape()
        #NOTE: this was from a time when mouse modification was possible. Leave for later use.
        #self.path = QtGui.QPainterPath()
        #self.pathRect = QtCore.QRectF(x, y-1, w, h+4) #this is directly related to inits parameter x, y, w, h
        #self.path.addRect(self.pathRect)
        #self.setCursor(QtCore.Qt.SizeHorCursor)

        #Different color when the duration was modified by the user
        inactive = "green" if noteExportObject["manualOverride"] else "black"
        self.inactiveColor = QtGui.QColor(inactive)
        self.setBrush(self.inactiveColor)

        #Pen for the borders
        pen = QtGui.QPen()
        pen.setWidth(0)
        pen.setColor(QtGui.QColor("darkGrey"))
        self.setPen(pen)

        #Since accidentals are part of the notehead we need one here. It doesn't matter if the traditional notehead already has one
        if noteExportObject["accidental"]: #0 means no difference to keysig
            self.accidental = GuiNote.createAccidentalGraphicsItem(self, noteExportObject["accidental"])
            self.accidental.setPos(0, 0) #not analogue to the notehead acciental position because here we choose the rectangle as parent
            self.accidental.setParentItem(self)


class GuiRectangleVelocity(QtWidgets.QGraphicsRectItem):
    """Displays the velocity of one note when in rectangle view mode.

    The Y-position is fixed on the staff and not bound to the rectangle notehead.
    This makes it possible for the user to compare velocities visually.

    """
    def __init__(self, parent, noteExportObject):
        #Dimensions and Position
        x = noteExportObject["leftModInTicks"] / constantsAndConfigs.ticksToPixelRatio #same as GuiRectangleNotehead
        y = -1 * (noteExportObject["velocity"] / constantsAndConfigs.velocityToPixelRatio)
        w = (noteExportObject["rightModInTicks"] + noteExportObject["completeDuration"] - noteExportObject["leftModInTicks"]) / constantsAndConfigs.ticksToPixelRatio #same as GuiRectangleNotehead
        h = noteExportObject["velocity"] / constantsAndConfigs.velocityToPixelRatio #velocityToPixelRation has its own compress factor since values can go from 0 to 127
        super().__init__(x, y, w, h)
        self.setPos(0, 2*constantsAndConfigs.stafflineGap) #The position is fixed to the staff, not to the GuiRectangleNotehead.
        self.setParentItem(parent)

        #Prepare self.shape()
        #NOTE: this was from a time when mouse modification was possible. Leave for later use.
        #self.path = QtGui.QPainterPath()
        #self.pathRect = QtCore.QRectF(x, y-1, w, h+4) #this is directly related to inits parameter x, y, w, h
        #self.path.addRect(self.pathRect)
        #self.setCursor(QtCore.Qt.SizeHorCursor)

        #Colors and Apperance
        inactive = "green" if noteExportObject["velocityManualOverride"] else "lightGray"
        self.inactiveVelocityColor = QtGui.QColor(inactive)
        self.activeVelocityColor =  QtGui.QColor("darkCyan")
        self.setBrush(self.inactiveVelocityColor)
        self.setOpacity(0.3)

        #Pen for the borders
        #The pen interferes with the precise appearance we want for live notes
        pen = QtGui.QPen()
        pen.setCapStyle(QtCore.Qt.RoundCap)
        pen.setJoinStyle(QtCore.Qt.RoundJoin)
        pen.setWidth(2)
        pen.setColor(QtGui.QColor("black"))
        self.setPen(pen)

        #Attach a label that displays the velocity as midi value 0-127
        #label = QtWidgets.QGraphicsSimpleTextItem(str(noteExportObject["velocity"]))
        #label.setPos(3, -3)
        #label.setParentItem(parent)

        #Misc Qt Flags
        self.setAcceptHoverEvents(False) #Make this explicit so it will be remembered that the gui rectangle velocity is a display only.


class GuiRectangle(QtWidgets.QGraphicsItem):
    """An alternative notehead, used to change velocity and duration
    finetuning.

    This is a GraphicsItem with two main components:
        The notehead which displays the duration and pitch of the note
        The Velocity which displays only the velocity.
    Both components share their Y position in the staff so they are one item.

    The notehead rectangles have full opacity and can't layer.
    The velocity rectangles layer and only a grahpical highlight shows the active one.
    """

    def __init__(self, noteExportObject):
        super().__init__()
        self.setFlag(QtWidgets.QGraphicsItem.ItemHasNoContents, True) #only child items. Without this we get notImplementedError: QGraphicsItem.paint() is abstract and must be overridden
        self.notehead = GuiRectangleNotehead(parent = self, noteExportObject = noteExportObject)
        self.velocity = GuiRectangleVelocity(parent = self, noteExportObject = noteExportObject)
        self.notehead.setZValue(10) #within GuiRectangle

    def boundingRect(self):
        return self.childrenBoundingRect()

class GuiNote(QtWidgets.QGraphicsItem):
    def __init__(self, noteExportObject, directionRightAndUpwards):
        super(GuiNote, self).__init__()
        self.setFlag(QtWidgets.QGraphicsItem.ItemHasNoContents, True) #only child items. Without this we get notImplementedError: QGraphicsItem.paint() is abstract and must be overridden
        self.createNote(noteExportObject, directionRightAndUpwards)

    def boundingRect(self):
        return self.childrenBoundingRect()

    def createNote(self, noteExportObject, directionRightAndUpwards):
        #note head
        self.noteExportObject = noteExportObject
        #self.noteHead = GuiNote.noteheads[noteExportObject["notehead"]]()
        self.noteHead = self.createNoteheadGraphicsItem(noteExportObject["notehead"])
        self.noteHead.setPos(0, constantsAndConfigs.stafflineGap * noteExportObject["dotOnLine"] / 2)
        self.noteHead.setParentItem(self)
        #Ledger lines need to be done in the chord to not be redundant and work for multiple notes.
        if noteExportObject["accidental"]: #0 means no difference to keysig
            self.accidental = self.createAccidentalGraphicsItem(noteExportObject["accidental"])
            self.accidental.setPos(0, constantsAndConfigs.stafflineGap * noteExportObject["dotOnLine"] / 2)
            self.accidental.setParentItem(self)

        for dot in range(noteExportObject["dots"]):
             d = QtSvg.QGraphicsSvgItem(":svg/dot.svg")
             d.setPos((dot+1)*4+6, constantsAndConfigs.stafflineGap * noteExportObject["dotOnLine"] / 2)
             d.setParentItem(self)


        #Draw the tuplet. There are no nested tuplets in Laborejo.
        if noteExportObject["tuplet"]:
            upper, lower = noteExportObject["tuplet"]
            tuplet = GuiTupletNumber(upper, lower)
            if directionRightAndUpwards:
                tuplet.setPos(3, -6)
            else:
                tuplet.setPos(3, 6)
            tuplet.setParentItem(self.noteHead)

        if noteExportObject["durationKeyword"]:
            self.durationKeywordGlyph = GuiNote.durationKeywords[noteExportObject["durationKeyword"]](noteExportObject)
            if directionRightAndUpwards:
                self.durationKeywordGlyph.setPos(3, 6)
            else:
                self.durationKeywordGlyph.setPos(3, -6)

            self.durationKeywordGlyph.setParentItem(self.noteHead)


    def createNoteheadGraphicsItem(self, number):
        item = QtSvg.QGraphicsSvgItem()
        item.setSharedRenderer(GuiNote.noteheads[number])
        return item

    def createAccidentalGraphicsItem(self, number):
        item = QtSvg.QGraphicsSvgItem()
        item.setSharedRenderer(GuiNote.accidentals[number])
        return item

    durationKeywords = { #0 is default and has no markers.
        api.D_STACCATO : lambda noteExportObject: QtSvg.QGraphicsSvgItem(":svg/scriptsStaccato.svg"),
        api.D_TENUTO : lambda noteExportObject: QtWidgets.QGraphicsSimpleTextItem("-"), #TODO: tenuto glyph
        api.D_TIE : lambda noteExportObject: GuiTieCurveGraphicsItem(noteExportObject),
        }

    noteheads = {
        4 :  QtSvg.QSvgRenderer(":svg/noteheadsBlack.svg"), #and 8, 16, 32, ...
        2 :  QtSvg.QSvgRenderer(":svg/noteheadsHalf.svg"),
        1 :  QtSvg.QSvgRenderer(":svg/noteheadsWhole.svg"),
        0 :  QtSvg.QSvgRenderer(":svg/noteheadsBrevis.svg"),
        -1 : QtSvg.QSvgRenderer(":svg/noteheadsLonga.svg"),
        -2 : QtSvg.QSvgRenderer(":svg/noteheadsMaxima.svg"),
        }

    #Accidental .svg are shifted to the left a bit already so we can place them on the same x-coordinates as the notes.
    accidentals = {
         1 :   QtSvg.QSvgRenderer(":svg/accidentalsNatural.svg"),
         10 :  QtSvg.QSvgRenderer(":svg/accidentalsSharp.svg"),
         20 :  QtSvg.QSvgRenderer(":svg/accidentalsDoublesharp.svg"),
         -10 : QtSvg.QSvgRenderer(":svg/accidentalsFlat.svg"),
         -20 : QtSvg.QSvgRenderer(":svg/accidentalsFlatFlat.svg"),
        }


    def stretchXCoordinates(self, factor):
        """Reposition the items on the X axis.
        Call goes through all parents/children, starting from ScoreView._stretchXCoordinates.
        Docstring there."""
        try:
            self.durationKeywordGlyph.stretchXCoordinates(factor)
        except AttributeError: #does not have any attachements or the attachment does not have the stretch function (e.g. staccato)
            pass

#Top level items, all based on GuiItem.

class GuiChord(GuiItem):
    """staticItem is a chord Dict which has another list of dicts:
    staticItem["notelist"].
    Each note with its notehead, accidental etc is in there.
    This means that a chord can consist of multiple durations as well
    as pitches, of course.

    For spacing calculations the chord itself gives us a duration as
    well."""

    def __init__(self, staticItem,):
        super().__init__(staticItem)
        self.staticItem = staticItem
        self.rectangles = []
        self.notes = []
        self.stem = None #stored for rectangle/head switching
        self.flag = None #stored for rectangle/head switching
        self.beamGroupGlyph = None #stored for rectangle/head switching
        #The actual beams are in the track

        self.createRectanglesFromData()
        self.createNotesFromData()
        self.createGenericFromData()
        self.updateVisibility()


    ledgerPen = QtGui.QPen(QtCore.Qt.SolidLine)
    ledgerPen.setCapStyle(QtCore.Qt.RoundCap)
    ledgerPen.setWidth(1)

    stemPen = QtGui.QPen(QtCore.Qt.SolidLine)
    stemPen.setCapStyle(QtCore.Qt.RoundCap)
    stemPen.setWidth(1.5)

    flags = { #QGraphicsItems can only be used once so we have to save a class constructor here instead of an instance.
        1024 : lambda: QtSvg.QGraphicsSvgItem(":svg/flag1024.svg"), #TODO
        512 : lambda: QtSvg.QGraphicsSvgItem(":svg/flag512.svg"), #TODO
        256 : lambda: QtSvg.QGraphicsSvgItem(":svg/flag256.svg"),
        128 : lambda: QtSvg.QGraphicsSvgItem(":svg/flag128.svg"),
        64 : lambda: QtSvg.QGraphicsSvgItem(":svg/flag64.svg"),
        32 : lambda: QtSvg.QGraphicsSvgItem(":svg/flag32.svg"),
        16 : lambda: QtSvg.QGraphicsSvgItem(":svg/flag16.svg"),
        8 : lambda: QtSvg.QGraphicsSvgItem(":svg/flag8.svg"),
        -1024 : lambda: QtSvg.QGraphicsSvgItem(":svg/flag1024i.svg"), #TODO
        -512 : lambda: QtSvg.QGraphicsSvgItem(":svg/flag512i.svg"), #TODO
        -256 : lambda: QtSvg.QGraphicsSvgItem(":svg/flag256i.svg"),
        -128 : lambda: QtSvg.QGraphicsSvgItem(":svg/flag128i.svg"),
        -64 : lambda: QtSvg.QGraphicsSvgItem(":svg/flag64i.svg"),
        -32 : lambda: QtSvg.QGraphicsSvgItem(":svg/flag32i.svg"),
        -16 : lambda: QtSvg.QGraphicsSvgItem(":svg/flag16i.svg"),
        -8 : lambda: QtSvg.QGraphicsSvgItem(":svg/flag8i.svg"),
        }

    def createGenericFromData(self):
        """Graphics for both rectangles and noteheads"""

        #Ledger Lines
        below, above = self.staticItem["ledgerLines"]

        for i in range(below):
            line = QtWidgets.QGraphicsLineItem(QtCore.QLineF(0, 0, 12, 0))
            line.setPen(GuiChord.ledgerPen)
            line.setParentItem(self)
            line.setPos(constantsAndConfigs.negativeMagicPixel, i * constantsAndConfigs.stafflineGap + constantsAndConfigs.stafflineGap*3)

        for i in range(above):
            line = QtWidgets.QGraphicsLineItem(QtCore.QLineF(0, 0, 12, 0))
            line.setPen(GuiChord.ledgerPen)
            line.setParentItem(self)
            line.setPos(constantsAndConfigs.negativeMagicPixel, -1 * i * constantsAndConfigs.stafflineGap - constantsAndConfigs.stafflineGap*3)

        #Display relative channel changes
        if not self.staticItem["midiChannelOffset"] == 0:
            channelGlyph = QtWidgets.QGraphicsSimpleTextItem("ch{0:+}".format(self.staticItem["midiChannelOffset"]))
            channelGlyph.setScale(0.75)
            channelGlyph.setParentItem(self)
            channelGlyph.setPos(0, -5*constantsAndConfigs.stafflineGap)

    def createRectanglesFromData(self):
        """Graphics only for rectangles"""
        for noteExportObject in self.staticItem["notelist"]:
            if not noteExportObject["tie"] == "notFirst":
                #Tied notes, except the first, do not produce rectangles because the first note gets an extra large rectangle.
                rectangle = GuiRectangle(noteExportObject)
                self.rectangles.append(rectangle)
                rectangle.setParentItem(self)

    def createNotesFromData(self):
        """Graphics only for noteheads"""
        if self.staticItem["beam"]:
            stemOrBeam = self.staticItem["beam"]
        else:
            stemOrBeam = self.staticItem["stem"]

        #stem[2] is left/-1 or right/1 stem shifting.
        #stemOrBeam = (starting point, length, direction)  #0 is middle line, but the stem for 0 begins at -1, which is the room above.
        #2 is treble-g which stem begins at 1.
        #negative numbers are above the middle line with the same -1 stem offset, compared to the note.
        #These numbers are calculated by the backend to span the whole chord. We just need to draw the stem here.
        #For beam groups the length is different for each member because they get shorter/longer with each ascending/descending note

        if stemOrBeam and stemOrBeam[2] > 0:
            self.directionRightAndUpwards = False
        else:
            self.directionRightAndUpwards = True

        #Stem - Both for groups and standalone.
        if stemOrBeam: #may be an empty tuple for whole notes and brevis
            line = QtWidgets.QGraphicsLineItem(QtCore.QLineF(0, constantsAndConfigs.stafflineGap * stemOrBeam[1]/2, 0, 0)) #x1, y1, x2, y2
            line.setPen(GuiChord.stemPen)
            self.stem = line #store as persistent item. Otherwise qt will delete it.
            line.setParentItem(self)
            line.setPos(constantsAndConfigs.magicPixel + stemOrBeam[2]*3, constantsAndConfigs.stafflineGap * stemOrBeam[0] / 2) #stem[2] is left/-1 or right/1 shifting.  #4 is the middle

            #Flags
            if not self.staticItem["beam"] and self.staticItem["flag"]:
                assert self.stem
                flag = GuiChord.flags[self.staticItem["flag"]]() #the graphic.
                flag.setParentItem(self)
                self.flag = flag #store as persistent item. Otherwise qt will delete it.
                flag.setPos(self.stem.pos().x(), self.stem.line().p1().y() + self.stem.pos().y()) #we already know where the stem-line is.

        #Check if this item is the start or end of a beam group and mark it with a textitem (lilypond syntax [ ])
        if self.staticItem["beamGroup"]:
            if self.staticItem["beamGroup"] == "open":
                beamGroupGlyph = QtWidgets.QGraphicsSimpleTextItem("[")
                beamGroupGlyph.setParentItem(self)
                beamGroupGlyph.setPos(0, -5*constantsAndConfigs.stafflineGap)
            elif self.staticItem["beamGroup"] == "close":
                beamGroupGlyph = QtWidgets.QGraphicsSimpleTextItem("]")
                beamGroupGlyph.setParentItem(self)
                beamGroupGlyph.setPos(constantsAndConfigs.magicPixel, -5*constantsAndConfigs.stafflineGap)
            self.beamGroupGlyph = beamGroupGlyph

        #Actual Noteheads
        lastDotOnLine = float("inf")
        for noteExportObject in self.staticItem["notelist"]:
            #notes in a chord come in the order highest to lowest
            #determine if we have two neighbouring noteheads. If yes we shift one of the heads to the right
            assert lastDotOnLine >= noteExportObject["dotOnLine"], (lastDotOnLine, noteExportObject["dotOnLine"]) #If this fails means the engine function did not call Chord.notelist.sort() after modification
            if lastDotOnLine - noteExportObject["dotOnLine"] == 1:
                moveThisNoteheadToTheRight = True
                lastDotOnLine = noteExportObject["dotOnLine"]
                lastDotOnLine += 1 #we shift the current notehead. The next pair would be no problem, even if neighbours.
            else:
                moveThisNoteheadToTheRight = False
                lastDotOnLine = noteExportObject["dotOnLine"]

            note = GuiNote(noteExportObject, self.directionRightAndUpwards)
            self.notes.append(note) #we need them later for switching between rectangles and noteheads
            note.setParentItem(self)
            if moveThisNoteheadToTheRight:
                note.setX(note.pos().x() + 6) #6 pixels.


    def stretchXCoordinates(self, factor):
        """Reposition the items on the X axis.
        Call goes through all parents/children, starting from ScoreView._stretchXCoordinates.
        Docstring there."""
        for rectNote in self.rectangles:
            stretchRect(rectNote.notehead, factor)
            stretchRect(rectNote.velocity, factor)
        for note in self.notes: #this is for ties only at the moment, the rest does not stretch
            note.stretchXCoordinates(factor)

    def updateVisibility(self):
        """decide during creation what part is visible at the moment. aka. are we in notehead
        or rectangle mode.
        Beams visibility is done in the track function createBeams"""

        if constantsAndConfigs.noteHeadMode:
            for r in self.rectangles:
                r.hide()
            for n in self.notes:
                n.show()
            if self.flag:
                self.flag.show()
            if self.stem:
                self.stem.show()
            if self.beamGroupGlyph:
                #This is about the start and end marker [  ] .The beam itself is in the track
                self.beamGroupGlyph.show()
        else: #rectangle mode.
            for r in self.rectangles:
                r.show()
            for n in self.notes:
                n.hide()
            if self.flag:
                self.flag.hide()
            if self.stem:
                self.stem.hide()
            if self.beamGroupGlyph:
                #This is about the start and end marker [  ] .The beam itself is in the track
                self.beamGroupGlyph.hide()

class GuiLiveNote(QtWidgets.QGraphicsRectItem):
    """A pure rectangle note, meant for live recording."""
    #TODO: Velocity indicator?

    instances = []

    def __init__(self, parent, liveNoteData):
        self.__class__.instances.append(self)
        self.liveNoteData = liveNoteData
        #Dimensions and Position
        x = 0 #A live note is always literally where it sounds
        y = -1 * constantsAndConfigs.stafflineGap / 2
        w = liveNoteData["duration"] / constantsAndConfigs.ticksToPixelRatio
        h = constantsAndConfigs.stafflineGap
        super().__init__(x, y, w, h)
        self.setY(constantsAndConfigs.stafflineGap * liveNoteData["dotOnLine"] / 2)
        self.setParentItem(parent)

        self.setBrush(QtGui.QColor("red"))

        #Pen for the borders
        pen = QtGui.QPen()
        pen.setWidth(0)
        self.setPen(pen)

        if liveNoteData["accidental"]: #0 means no difference to keysig
            self.accidental = GuiNote.createAccidentalGraphicsItem(self, liveNoteData["accidental"])
            self.accidental.setPos(0, 0) #not analogue to the notehead acciental position because here we choose the rectangle as parent
            self.accidental.setParentItem(self)

    def update(self, duration):
        r = self.rect()
        r.setRight(duration / constantsAndConfigs.ticksToPixelRatio)
        self.setRect(r)

class GuiRest(GuiItem):
    def __init__(self, staticItem):
        super(GuiRest, self).__init__(staticItem)
        self.createGraphicItemsFromData()

    rests = { #QGraphicsItems can only be used once so we have to save a class constructor here instead of an instance.
        -1 : lambda: QtSvg.QGraphicsSvgItem(":svg/restLonga.svg"),
        -2 : lambda: QtSvg.QGraphicsSvgItem(":svg/restMaxima.svg"),
        0 : lambda: QtSvg.QGraphicsSvgItem(":svg/restBrevis.svg"),
         1 : lambda: QtSvg.QGraphicsSvgItem(":svg/rest1.svg"),
         2 : lambda: QtSvg.QGraphicsSvgItem(":svg/rest2.svg"),
         4 : lambda: QtSvg.QGraphicsSvgItem(":svg/rest4.svg"),
         8 : lambda: QtSvg.QGraphicsSvgItem(":svg/rest8.svg"),
        16 : lambda: QtSvg.QGraphicsSvgItem(":svg/rest16.svg"),
        32 : lambda: QtSvg.QGraphicsSvgItem(":svg/rest32.svg"),
        64 : lambda: QtSvg.QGraphicsSvgItem(":svg/rest64.svg"),
        128 : lambda: QtSvg.QGraphicsSvgItem(":svg/rest128.svg"),
        256 : lambda: QtSvg.QGraphicsSvgItem(":svg/rest256.svg"), #TODO
        512 : lambda: QtSvg.QGraphicsSvgItem(":svg/rest512.svg"), #TODO
        1024 : lambda: QtSvg.QGraphicsSvgItem(":svg/rest1024.svg"), #TODO
        }

    def createGraphicItemsFromData(self):
        self.glyph = GuiRest.rests[self.staticItem["rest"]]()
        self.glyph.setParentItem(self)

        for dot in range(self.staticItem["dots"]):
             d = QtSvg.QGraphicsSvgItem(":svg/dot.svg")
             d.setPos((dot+1)*4+6, -2)
             d.setParentItem(self)

        if self.staticItem["tuplet"]:
            upper, lower = self.staticItem["tuplet"]
            tuplet = GuiTupletNumber(upper, lower)
            tuplet.setParentItem(self)
            tuplet.setPos(2, -4-constantsAndConfigs.stafflineGap*2)

class GuiKeySignature(GuiItem):
    def __init__(self, staticItem):
        super(GuiKeySignature, self).__init__(staticItem)
        self.createGraphicItemsFromData()

    def createGraphicItemsFromData(self):
        """staticItems accidentalsOnLines is ordered. We receive the
        correct accidental order from left to right and only need
        to place the gaps"""
        for i, (accidentalOnLine, pitchMod) in enumerate(self.staticItem["accidentalsOnLines"]):
            glyph = GuiKeySignature.accidentals[pitchMod]()
            glyph.setParentItem(self)
            glyph.setPos(constantsAndConfigs.magicPixel + i*1.5*constantsAndConfigs.magicPixel, constantsAndConfigs.stafflineGap * accidentalOnLine / 2) #These accidental graphics are shifted to the left in the .svg so they can be placed just as noteheads and match automatically. So we set them +x here.

        if not self.staticItem["accidentalsOnLines"]:
            #No accidentals in the keysig (C-Major, D-Dorian etc.) gets a big natural sign.
            self.bigNatural = GuiKeySignature.accidentals[0]()  #"big natural"... hö hö hö hö
            self.bigNatural.setParentItem(self)
            self.bigNatural.setPos(constantsAndConfigs.magicPixel, constantsAndConfigs.stafflineGap * -1)

        self.rootGlyph = QtWidgets.QGraphicsSimpleTextItem(pitch.baseNotesToBaseNames[self.staticItem["root"]])
        self.rootGlyph.setParentItem(self)
        self.rootGlyph.setPos(constantsAndConfigs.negativeMagicPixel, 2*constantsAndConfigs.stafflineGap)


    accidentals = { #QGraphicsItems can only be used once so we have to save a class constructor here instead of an instance.
         0 : lambda: QtSvg.QGraphicsSvgItem(":svg/accidentalsNatural.svg"),  #normaly we don't receive those, but it is possible to ask for explicit naturals in the backend and in lilypond.
         10 : lambda: QtSvg.QGraphicsSvgItem(":svg/accidentalsSharp.svg"),
         20 : lambda: QtSvg.QGraphicsSvgItem(":svg/accidentalsDoublesharp.svg"),
         -10 : lambda: QtSvg.QGraphicsSvgItem(":svg/accidentalsFlat.svg"),
         -20 : lambda: QtSvg.QGraphicsSvgItem(":svg/accidentalsFlatFlat.svg"),
        }

class GuiClef(GuiItem):
    def __init__(self, staticItem):
        super(GuiClef, self).__init__(staticItem)
        self.createGraphicItemsFromData()

    def createGraphicItemsFromData(self):
        self.glyph = GuiClef.clefs[self.staticItem["clef"]]()
        self.glyph.setParentItem(self)
        self.glyph.setPos(-5, -5 * constantsAndConfigs.stafflineGap)

        #self.text = QtWidgets.QGraphicsSimpleTextItem(self.staticItem["clef"].title())
        #self.text.setParentItem(self)
        #self.text.setPos(0, -5 * constantsAndConfigs.stafflineGap)

        self.marker = GuiPositionMarker(-5)
        self.marker.setParentItem(self)
        self.marker.setPos(0,0)

    clefs = { #QGraphicsItems can only be used once so we have to save a class constructor here instead of an instance.
         "treble" : lambda: QtSvg.QGraphicsSvgItem(":svg/clefTreble.svg"),
         "treble_8" : lambda: QtSvg.QGraphicsSvgItem(":svg/clefTreble_8.svg"),
         "treble^8" : lambda: QtSvg.QGraphicsSvgItem(":svg/clefTreble^8.svg"),
         "bass" : lambda: QtSvg.QGraphicsSvgItem(":svg/clefBass.svg"),
         "bass_8" : lambda: QtSvg.QGraphicsSvgItem(":svg/clefBass_8.svg"),
         "alto" : lambda: QtSvg.QGraphicsSvgItem(":svg/clefAlto.svg"),
         "midiDrum" : lambda: QtSvg.QGraphicsSvgItem(":svg/clefPercussion.svg"),
         "percussion" : lambda: QtSvg.QGraphicsSvgItem(":svg/clefPercussion.svg"),
         }

class GuiTimeSignature(GuiItem):
    def __init__(self, staticItem):
        raise Exception("Don't use this item. It will be deleted in future versions. Use a Metrical Instruction")
        super().__init__(staticItem)
        self.setPos(0, constantsAndConfigs.stafflineGap)
        self.createGraphicItemsFromData()

    def createGraphicItemsFromData(self):
        #higher number, how many notes
        for i, charInt in enumerate(str(self.staticItem["nominator"])):
            n = GuiTimeSignature.digits[int(charInt)]
            charInt = QtSvg.QGraphicsSvgItem()
            charInt.setSharedRenderer(GuiTimeSignature.renderer)
            charInt.setElementId(n)
            charInt.setPos(i*2*constantsAndConfigs.magicPixel, -1*constantsAndConfigs.stafflineGap)
            charInt.setParentItem(self)

        #lower number, which note type
        for i, charInt in enumerate(str(self.staticItem["denominator"])): #2^n
            n = GuiTimeSignature.digits[int(charInt)]
            charInt = QtSvg.QGraphicsSvgItem()
            charInt.setSharedRenderer(GuiTimeSignature.renderer)
            charInt.setElementId(n)
            charInt.setPos(i*2*constantsAndConfigs.magicPixel, constantsAndConfigs.stafflineGap)
            charInt.setParentItem(self)

    renderer = QtSvg.QSvgRenderer(":svg/numbers.svg")
    digits = "zero one two three four five six seven eight nine".split() #a list


class GuiMetricalInstruction(GuiItem):
    def __init__(self, staticItem):
        super().__init__(staticItem)

        if staticItem["oneMeasureInTicks"] == 0:
            #Basically just a manual barline
            displayString = "X"
            markerPosition = -2
            markerHeight = 4

        else:
            #Format the Tree of Instructions
            realTree = eval(staticItem["treeOfInstructions"])
            if not type(realTree[0]) is tuple: #first order measure. like 3/4 or 2/4 or anything with just one stressed position
                realTree = (realTree, ) #created nested iterator
            r = self.treeToPrettyString(realTree)
            r = r.__repr__()[1:-1].replace("'", "")
            onlyTopLevelElements = not any(isinstance(el, list) for el in r)
            if onlyTopLevelElements:
                r = r.replace(",", "")

            displayString = "Metrical " + r
            markerPosition = -2
            markerHeight = 4


        #A metrical instruction is by definition at the beginning of a measure
        #We therefore need to place the text above the barnumber
        self.text = QtWidgets.QGraphicsSimpleTextItem(displayString)
        self.text.setParentItem(self)
        self.text.setPos(0, -7 * constantsAndConfigs.stafflineGap)

        self.marker = GuiPositionMarker(markerHeight, markerPosition)
        self.marker.setParentItem(self)
        self.marker.setPos(0,0)

    def treeToPrettyString(self, tree):
        """Convert a metrical instruction into a pretty string with note symbols
        For example
            ((53760, 53760), (53760, 53760))
                which is ((D4, D4), (D4, D4))

            into
            2𝅘𝅥 2𝅘𝅥

        """
        result = []

        for element in tree:
            if type(element) is tuple:
                l = self.treeToPrettyString(element)
                l = ["{}{}".format(sum(1 for e in grouper), note) for note, grouper in groupby(l)]
                if len(l) == 1:
                    l = l[0] #makes formatting clearerm
                result.append(l)
            else:
                assert type(element) is int
                noteString = constantsAndConfigs.realNoteDisplay[element]
                result.append(noteString)
        return result


class GuiBlockEndMarker(GuiItem):
    def __init__(self, staticItem):
        super(GuiBlockEndMarker, self).__init__(staticItem)
        self.glyph = QtSvg.QGraphicsSvgItem(":svg/blockEnd.svg")
        self.createGraphicItemsFromData()

    def createGraphicItemsFromData(self):
        self.glyph.setParentItem(self)

class GuiDynamicSignature(GuiItem):
    def __init__(self, staticItem):
        super(GuiDynamicSignature, self).__init__(staticItem)
        self.glyph = QtWidgets.QGraphicsSimpleTextItem(staticItem["keyword"])
        self.createGraphicItemsFromData()

    def createGraphicItemsFromData(self):
        self.glyph.setPos(0, 5*constantsAndConfigs.stafflineGap)
        self.glyph.setParentItem(self)

class GuiMultiMeasureRest(GuiItem):
    def __init__(self, staticItem):
        super(GuiMultiMeasureRest, self).__init__(staticItem)
        self.createGraphicItemsFromData()

    def createGraphicItemsFromData(self):
        w =  self.staticItem["completeDuration"] / constantsAndConfigs.ticksToPixelRatio - constantsAndConfigs.magicPixel*2

        if int(self.staticItem["numberOfMeasures"]) == self.staticItem["numberOfMeasures"]: #just an int?
            numberOfMeasuresString = str(int(self.staticItem["numberOfMeasures"]))
        else:
            numberOfMeasuresString = str(self.staticItem["numberOfMeasures"]) #include the decimal dot

        rect = QtWidgets.QGraphicsRectItem(constantsAndConfigs.magicPixel,0, w, constantsAndConfigs.stafflineGap) #x,y, w, h
        rect.setBrush(QtCore.Qt.black)
        rect.setParentItem(self)
        self.rect = rect #for stretching

        leftBlock = QtWidgets.QGraphicsRectItem(2, -1* constantsAndConfigs.stafflineGap, constantsAndConfigs.magicPixel, 3*constantsAndConfigs.stafflineGap)
        leftBlock.setBrush(QtCore.Qt.black)
        leftBlock.setParentItem(self)

        rightBlock = QtWidgets.QGraphicsRectItem(0, -1* constantsAndConfigs.stafflineGap, constantsAndConfigs.magicPixel, 3*constantsAndConfigs.stafflineGap) #x, y, w, h
        rightBlock.setBrush(QtCore.Qt.black)
        #rightBlock.setPos(w,0)
        rightBlock.setPos(rect.rect().right(),0)
        rightBlock.setParentItem(self)
        self.rightBlock = rightBlock #for stretching

        for i, char in enumerate(numberOfMeasuresString):
            if char == ".":
                continue #TODO: Maybe warn about that?  MMR below 1 are not valid but they are needed for editing purposes. Sometimes you need to go through such a value.
            else:
                n = GuiTimeSignature.digits[int(char)]
                char = QtSvg.QGraphicsSvgItem()
                char.setSharedRenderer(GuiTimeSignature.renderer)
                char.setElementId(n)
            #char.setPos(i*4*constantsAndConfigs.magicPixel + 4* constantsAndConfigs.magicPixel, -3*constantsAndConfigs.stafflineGap)
            char.setPos(leftBlock.rect().width(), -3*constantsAndConfigs.stafflineGap)
            char.setScale(1.5)
            char.setParentItem(self)

        self.setPos(0, -3 * constantsAndConfigs.stafflineGap)

    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.rect, factor)
        self.rightBlock.setX(self.rightBlock.pos().x() * factor)
        #self.char.setX(self.char.pos().x() * factor)

class GuiLegatoSlur(GuiItem):
    def __init__(self, staticItem):
        super().__init__(staticItem)
        if staticItem["slur"] == "open":
            self.glyph = QtWidgets.QGraphicsSimpleTextItem("(")
            self.glyph.setPos(2,-5*constantsAndConfigs.stafflineGap)
        else:
            assert staticItem["slur"] == "close"
            self.glyph = QtWidgets.QGraphicsSimpleTextItem(")")
            self.glyph.setPos(-4,-5*constantsAndConfigs.stafflineGap)

        self.createGraphicItemsFromData()

    def createGraphicItemsFromData(self):
        #self.glyph.setPos(0, -5*constantsAndConfigs.stafflineGap)
        self.glyph.setParentItem(self)

class GuiGenericText(GuiItem):
    def __init__(self, staticItem):
        super().__init__(staticItem)
        self.glyph = QtWidgets.QGraphicsSimpleTextItem(staticItem["UIstring"])
        self.glyph.setPos(0, -5*constantsAndConfigs.stafflineGap)
        self.glyph.setParentItem(self)

def staticItem2Item(staticItem):
    typ = staticItem["type"]
    if typ == "Chord":
        return GuiChord(staticItem)
    elif typ == "Rest":
        return GuiRest(staticItem)
    elif typ == "LegatoSlur":
        return GuiLegatoSlur(staticItem)
    elif typ == "MultiMeasureRest":
        return GuiMultiMeasureRest(staticItem)
    elif typ == "DynamicSignature":
        return GuiDynamicSignature(staticItem)
    elif typ == "Clef":
        return GuiClef(staticItem)
    elif typ == "KeySignature":
        return GuiKeySignature(staticItem)
    #elif typ == "TimeSignature":
    #    return GuiTimeSignature(staticItem)

    elif typ == "MetricalInstruction":
        return GuiMetricalInstruction(staticItem)
    elif typ == "BlockEndMarker":
        return GuiBlockEndMarker(staticItem)
    elif typ in ("InstrumentChange", "ChannelChange", "LilypondText"):
        return GuiGenericText(staticItem)
    else:
        raise ValueError("Unknown Item Type:", staticItem)
