Exemple #1
0
 def __init__(self, parent):
     super(QScore, self).__init__(parent)
     self._scale = 1
     self._qStaffs = []
     self._qSections = []
     self._properties = parent.songProperties
     self._score = None
     self._dirty = None
     self._currentKey = None
     self._ignoreNext = False
     self.measureClipboard = []
     self._playingMeasure = None
     self._nextMeasure = None
     self._dragSelection = DragSelection(self)
     self._saved = False
     self._undoStack = QtGui.QUndoStack(self)
     self._inMacro = False
     self._macroCanReformat = False
     self._undoStack.canUndoChanged.connect(self.canUndoChanged)
     self._undoStack.undoTextChanged.connect(self.undoTextChanged)
     self._undoStack.canRedoChanged.connect(self.canRedoChanged)
     self._undoStack.redoTextChanged.connect(self.redoTextChanged)
     self._metaData = QMetaData(self)
     self._metaData.setPos(self.xMargins, self.yMargins)
     self._metaData.setVisible(self._properties.metadataVisible)
     self.metadataChanged.connect(lambda n_, v_: self.reBuild())
     self.paperSizeChanged.connect(parent.setPaperSize)
     self._kitData = QKitData(self)
     self._kitData.setPos(self.xMargins, 0)
     self._kitData.setVisible(self._properties.kitDataVisible)
     if parent.filename is not None:
         if not self.loadScore(parent.filename, quiet=False):
             parent.filename = None
             self.newScore(None)
     else:
         self.newScore(None)
     self.defaultCountChanged.connect(parent.setDefaultCount)
     self.spacingChanged.connect(parent.setSystemSpacing)
     self.sectionsChanged.connect(parent.setSections)
     self._properties.connectScore(self)
     self._potentials = []
     self._shortcutMemo = _HeadShortcutsMap(self._score.drumKit)
     self._stateMachine = DBStateMachine(Waiting, self)
Exemple #2
0
 def __init__(self, parent):
     super(QScore, self).__init__(parent)
     self._scale = 1
     self._qStaffs = []
     self._qSections = []
     self._properties = parent.songProperties
     self._score = None
     self._dirty = None
     self._currentKey = None
     self._ignoreNext = False
     self.measureClipboard = []
     self._playingMeasure = None
     self._nextMeasure = None
     self._dragSelection = DragSelection(self)
     self._saved = False
     self._undoStack = QtGui.QUndoStack(self)
     self._inMacro = False
     self._macroCanReformat = False
     self._undoStack.canUndoChanged.connect(self.canUndoChanged)
     self._undoStack.undoTextChanged.connect(self.undoTextChanged)
     self._undoStack.canRedoChanged.connect(self.canRedoChanged)
     self._undoStack.redoTextChanged.connect(self.redoTextChanged)
     self._metaData = QMetaData(self)
     self._metaData.setPos(self.xMargins,
                           self.yMargins)
     self._metaData.setVisible(self._properties.metadataVisible)
     self.metadataChanged.connect(lambda n_, v_: self.reBuild())
     self.paperSizeChanged.connect(parent.setPaperSize)
     self._kitData = QKitData(self)
     self._kitData.setPos(self.xMargins, 0)
     self._kitData.setVisible(self._properties.kitDataVisible)
     if parent.filename is not None:
         if not self.loadScore(parent.filename, quiet = False):
             parent.filename = None
             self.newScore(None)
     else:
         self.newScore(None)
     self.defaultCountChanged.connect(parent.setDefaultCount)
     self.spacingChanged.connect(parent.setSystemSpacing)
     self.sectionsChanged.connect(parent.setSections)
     self._properties.connectScore(self)
     self._potentials = []
     self._shortcutMemo = _HeadShortcutsMap(self._score.drumKit)
     self._stateMachine = DBStateMachine(Waiting, self)
Exemple #3
0
class QScore(QtGui.QGraphicsScene):
    def __init__(self, parent):
        super(QScore, self).__init__(parent)
        self._scale = 1
        self._qStaffs = []
        self._qSections = []
        self._properties = parent.songProperties
        self._score = None
        self._dirty = None
        self._currentKey = None
        self._ignoreNext = False
        self.measureClipboard = []
        self._playingMeasure = None
        self._nextMeasure = None
        self._dragSelection = DragSelection(self)
        self._saved = False
        self._undoStack = QtGui.QUndoStack(self)
        self._inMacro = False
        self._macroCanReformat = False
        self._undoStack.canUndoChanged.connect(self.canUndoChanged)
        self._undoStack.undoTextChanged.connect(self.undoTextChanged)
        self._undoStack.canRedoChanged.connect(self.canRedoChanged)
        self._undoStack.redoTextChanged.connect(self.redoTextChanged)
        self._metaData = QMetaData(self)
        self._metaData.setPos(self.xMargins,
                              self.yMargins)
        self._metaData.setVisible(self._properties.metadataVisible)
        self.metadataChanged.connect(lambda n_, v_: self.reBuild())
        self.paperSizeChanged.connect(parent.setPaperSize)
        self._kitData = QKitData(self)
        self._kitData.setPos(self.xMargins, 0)
        self._kitData.setVisible(self._properties.kitDataVisible)
        if parent.filename is not None:
            if not self.loadScore(parent.filename, quiet = False):
                parent.filename = None
                self.newScore(None)
        else:
            self.newScore(None)
        self.defaultCountChanged.connect(parent.setDefaultCount)
        self.spacingChanged.connect(parent.setSystemSpacing)
        self.sectionsChanged.connect(parent.setSections)
        self._properties.connectScore(self)
        self._potentials = []
        self._shortcutMemo = _HeadShortcutsMap(self._score.drumKit)
        self._stateMachine = DBStateMachine(Waiting, self)

    canUndoChanged = QtCore.pyqtSignal(bool)
    canRedoChanged = QtCore.pyqtSignal(bool)
    undoTextChanged = QtCore.pyqtSignal(str)
    redoTextChanged = QtCore.pyqtSignal(str)
    metadataChanged = QtCore.pyqtSignal(str, object)
    paperSizeChanged = QtCore.pyqtSignal(str)
    defaultCountChanged = QtCore.pyqtSignal(object)
    spacingChanged = QtCore.pyqtSignal(int)
    sectionsChanged = QtCore.pyqtSignal()
    showItem = QtCore.pyqtSignal(QGraphicsItem)
    dragHighlight = QtCore.pyqtSignal(bool)
    sceneFormatted = QtCore.pyqtSignal()
    playing = QtCore.pyqtSignal(bool)
    currentHeadsChanged = QtCore.pyqtSignal(QtCore.QString)
    statusMessageSet = QtCore.pyqtSignal(QtCore.QString)
    lilysizeChanged = QtCore.pyqtSignal(int)
    lilypagesChanged = QtCore.pyqtSignal(int)
    lilyFillChanged = QtCore.pyqtSignal(bool)
    lilyFormatChanged = QtCore.pyqtSignal(int)
    widthChanged = QtCore.pyqtSignal(int)

    def addCommand(self, command):
        if not self._inMacro:
            self._undoStack.beginMacro(command.description)
            self._undoStack.push(CheckUndo(self))
            if command.canReformat:
                self._addSaveStateCommand()
        self._undoStack.push(command)
        if not self._inMacro:
            if command.canReformat:
                self._addCheckStateCommand()
            self._undoStack.endMacro()
        self.dirty = not (self._undoStack.isClean() and self._saved)

    def addRepeatedCommand(self, name, command, arguments):
        self.beginMacro(name, command.canReformat)
        for args in arguments:
            self.addCommand(command(self, *args))
        self.endMacro()

    def beginMacro(self, name, canReformat = True):
        self._undoStack.beginMacro(name)
        self._inMacro = True
        self._undoStack.push(CheckUndo(self))
        self._macroCanReformat = canReformat
        if canReformat:
            self._addSaveStateCommand()

    def endMacro(self):
        if self._macroCanReformat:
            self._addCheckStateCommand()
        self._undoStack.endMacro()
        self._inMacro = False

    def _addSaveStateCommand(self):
        command = SaveFormatStateCommand(self)
        self._undoStack.push(command)

    def _addCheckStateCommand(self):
        command = CheckFormatStateCommand(self)
        self._undoStack.push(command)

    def undo(self):
        self._undoStack.undo()
        self.dirty = not (self._undoStack.isClean() and self._saved)

    def redo(self):
        self._undoStack.redo()
        self.dirty = not (self._undoStack.isClean() and self._saved)

    def startUp(self):
        self.metadataChanged.emit("width", self.scoreWidth)
        self.setCurrentHeads(None)

    def _getscoreWidth(self):
        if self._score is not None:
            return self._score.scoreData.width
        else:
            return None
    def _setscoreWidth(self, value):
        if self._score is None:
            return
        if self.scoreWidth != value:
            command = ScoreWidthCommand(self, value)
            self.clearDragSelection()
            self.addCommand(command)
    scoreWidth = property(fget = _getscoreWidth,
                          fset = _setscoreWidth)

    artist = _metaDataProperty("artist")
    artistVisible = _metaDataProperty("artistVisible")
    creator = _metaDataProperty("creator")
    creatorVisible = _metaDataProperty("creatorVisible")
    title = _metaDataProperty("title")
    bpm = _metaDataProperty("bpm")
    bpmVisible = _metaDataProperty("bpmVisible")

    @property
    def displayProperties(self):
        return self._properties

    def _getdirty(self):
        return self._dirty
    def _setdirty(self, value):
        if self._dirty != value:
            self._dirty = value
            self.dirtySignal.emit(self._dirty)
    dirty = property(fget = _getdirty, fset = _setdirty)
    dirtySignal = QtCore.pyqtSignal(bool)

    @property
    def kitSize(self):
        return len(self._score.drumKit)

    @property
    def lineOffsets(self):
        yOffsets = [drumIndex * self.ySpacing
                    for drumIndex in xrange(self.kitSize)]
        yOffsets.reverse()
        return yOffsets

    def _setScore(self, score):
        if score != self._score:
            score.formatScore(None)
            self._score = score
            self._shortcutMemo = _HeadShortcutsMap(score.drumKit)
            if score is not None:
                self.startUp()
            self._score.setCallBack(self.dataChanged)
            self._build()
            self._properties.lineSpacing = self._score.systemSpacing - 101
            self.paperSizeChanged.emit(self._score.paperSize)
            self.defaultCountChanged.emit(self._score.defaultCount)
            self.spacingChanged.emit(self._score.systemSpacing)
            self.lilysizeChanged.emit(self._score.lilysize)
            self.lilypagesChanged.emit(self._score.lilypages)
            self.lilyFillChanged.emit(self._score.lilyFill)
            self.lilyFormatChanged.emit(self._score.lilyFormat)
            self.sectionsChanged.emit()
            self._properties.newScore(self)
            self._kitData.setVisible(self._properties.kitDataVisible)
            self._metaData.setVisible(self._properties.metadataVisible)
            DBMidi.setKit(score.drumKit)
            self._undoStack.clear()
            self._undoStack.setClean()
            self._inMacro = False
            self.widthChanged.emit(self.scoreWidth)
            self.reBuild()
            self.dirty = False
            self._stateMachine = DBStateMachine(Waiting, self)
            self.scoreDisplayChanged.emit()

    @property
    def score(self):
        return self._score

    def _getscale(self):
        return self._scale
    def _setscale(self, value):
        if self._scale != value:
            self._scale = value
            yOffset = self.yMargins
            self._metaData.setPos(self.xMargins,
                                  yOffset)
            if self._properties.metadataVisible:
                yOffset += self._metaData.boundingRect().height()
            self._kitData.setPos(self.xMargins, yOffset)
            self._build()
            self.invalidate()
    scale = property(fget = _getscale, fset = _setscale)

    @property
    def xSpacing(self):
        return self._properties.xSpacing * self.scale

    @property
    def ySpacing(self):
        return self._properties.ySpacing * self.scale

    @property
    def lineSpacing(self):
        return self._properties.lineSpacing * self.scale

    @property
    def xMargins(self):
        return self._properties.xMargins * self.scale

    @property
    def yMargins(self):
        return self._properties.yMargins * self.scale

    def _build(self):
        self._clearStaffs()
        for staff in self._score.iterStaffs():
            self._addStaff(staff)
        for title in self._score.iterSections():
            self._addSection(title)
        self.placeStaffs()
        self.invalidate()

    scoreDisplayChanged = QtCore.pyqtSignal()

    @delayCall
    def reBuild(self, afterwards = None):
        if self._score.formatScore(None):
            self.scoreDisplayChanged.emit()
        self._build()
        if afterwards:
            afterwards()

    def checkFormatting(self):
        if self._score.formatScore(None):
            self.scoreDisplayChanged.emit()
            self.reBuild()

    def __iter__(self):
        return iter(self._qStaffs)

    def _clearStaffs(self):
        for qStaff in self._qStaffs:
            self.removeItem(qStaff)
        self._qStaffs = []
        for qSection in self._qSections:
            self.removeItem(qSection)
        self._qSections = []

    def _addStaff(self, staff):
        qStaff = QStaff(staff, len(self._qStaffs), self)
        self._qStaffs.append(qStaff)

    def _addSection(self, title):
        qSection = QSection(title, qScore = self)
        qSection.setIndex(len(self._qSections))
        self._qSections.append(qSection)

    def setSectionTitle(self, index, title):
        qSection = self._qSections[index]
        qSection.setTitle(title)

    def setPaperSize(self, newPaperSize):
        if newPaperSize != self._score.paperSize:
            command = SetPaperSizeCommand(self,
                                          newPaperSize)
            self.addCommand(command)

    def getQSection(self, sectionIndex):
        if 0 <= sectionIndex < len(self._qSections):
            return self._qSections[sectionIndex]

    def _getdefaultCount(self):
        return self._score.defaultCount
    def _setdefaultCount(self, newCount):
        if newCount != self._score.defaultCount:
            command = SetDefaultCountCommand(self, newCount)
            self.addCommand(command)
    defaultCount = property(_getdefaultCount,
                            _setdefaultCount)

    def _getsystemSpacing(self):
        return self._score.systemSpacing
    def _setsystemSpacing(self, value):
        if self._score.systemSpacing != value:
            command = SetSystemSpacingCommand(self, value)
            self.addCommand(command)
    systemSpacing = property(_getsystemSpacing, _setsystemSpacing)

    def placeStaffs(self, staffCall = QStaff.placeMeasures):
        xMargins = self.xMargins
        yMargins = self.yMargins
        lineSpacing = self.lineSpacing
        yOffset = self.yMargins
        if self._properties.metadataVisible:
            yOffset += self._metaData.boundingRect().height()
        if self._properties.kitDataVisible:
            self._kitData.setY(yOffset)
            yOffset += self._kitData.boundingRect().height()
        newSection = True
        sectionIndex = 0
        maxWidth = 0
        for qStaff in self:
            if newSection:
                newSection = False
                if sectionIndex < len(self._qSections):
                    qSection = self._qSections[sectionIndex]
                    sectionIndex += 1
                    qSection.setPos(xMargins, yOffset)
                    yOffset += qSection.boundingRect().height()
            newSection = qStaff.isSectionEnd()
            qStaff.setPos(xMargins, yOffset)
            if staffCall is not None:
                staffCall(qStaff)
            yOffset += qStaff.height() + lineSpacing
            maxWidth = max(maxWidth, qStaff.width())
            newSection = qStaff.isSectionEnd()
        self.setSceneRect(0, 0,
                          maxWidth + 2 * xMargins,
                          yOffset - lineSpacing + yMargins)
        self.sceneFormatted.emit()

    def xSpacingChanged(self):
        self.placeStaffs(QStaff.xSpacingChanged)

    def ySpacingChanged(self):
        self.placeStaffs(QStaff.ySpacingChanged)

    def lineSpacingChanged(self):
        self.placeStaffs(None)

    def sectionFontChanged(self):
        self._metaData.fontChanged()
        for qsection in self._qSections:
            qsection.setFont(self._properties.sectionFont)
        self.lineSpacingChanged()

    def metadataVisibilityChanged(self):
        self._metaData.setVisible(self._properties.metadataVisible)
        self.reBuild()

    def metadataFontChanged(self):
        with self.metaChange():
            self._metaData.fontChanged()

    def kitDataVisibleChanged(self):
        self._kitData.setVisible(self._properties.kitDataVisible)
        self.reBuild()

    def dataChanged(self, notePosition):
        staff = self._qStaffs[notePosition.staffIndex]
        staff.dataChanged(notePosition)
        self.scoreDisplayChanged.emit()

    def ignoreNextClick(self):
        self._ignoreNext = True

    def mousePressEvent(self, event):
        item = self.itemAt(event.scenePos())
        if not isinstance(item, QMeasure):
            self.clearDragSelection()
        event.ignore()
        if self._ignoreNext:
            self._ignoreNext = False
        else:
            super(QScore, self).mousePressEvent(event)

    def keyPressEvent(self, event):
        if not event.isAutoRepeat():
            if event.key() == QtCore.Qt.Key_Escape:
                self.sendFsmEvent(Escape())
            else:
                if self._currentKey == None and event.text():
                    self._currentKey = unicode(event.text())
                    self._highlightCurrentKeyHead()
                    self.update()
        return super(QScore, self).keyPressEvent(event)

    def keyReleaseEvent(self, event):
        if not event.isAutoRepeat():
            if event.key() != QtCore.Qt.Key_Escape:
                if unicode(event.text()) == self._currentKey:
                    self._currentKey = None
                    self._highlightCurrentKeyHead()
                    self.update()
        return super(QScore, self).keyReleaseEvent(event)

    def getCurrentHead(self):
        return self._shortcutMemo.getCurrentHead(self._currentKey)

    def setCurrentHeads(self, drumIndex):
        self._shortcutMemo.setDrumIndex(drumIndex)
        self._highlightCurrentKeyHead()

    def _highlightCurrentKeyHead(self):
        headText = self._shortcutMemo.getShortcutText(self._currentKey)
        self.currentHeadsChanged.emit(QtCore.QString(headText))

    def copyMeasures(self, np = None):
        if np is not None:
            self.measureClipboard = [self._score.copyMeasure(np)]
        elif self.hasDragSelection():
            self.measureClipboard = [self._score.copyMeasure(dragNP)
                                     for (unusedMeasure, unusedIndex, dragNP)
                                     in self.iterDragSelection()]

    def clearMeasures(self, np = None):
        if np is not None:
            command = ClearMeasureCommand(self, [np])
        else:
            command = ClearMeasureCommand(self,
                                          [dragNP for (unusedMeasure,
                                                       unusedIndex,
                                                       dragNP)
                                           in self.iterDragSelection()])
        self.addCommand(command)

    def deleteMeasures(self, np = None):
        if np is not None:
            command = DeleteMeasureCommand(self, np)
            self.clearDragSelection()
            self.addCommand(command)
        else:
            if not self.hasDragSelection():
                return
            start = self._dragSelection.start
            measureIndex = self._score.measurePositionToIndex(start)
            measures = list(self.iterDragSelection())
            self.clearDragSelection()
            self.beginMacro("delete measures")
            for unused in measures:
                command = DeleteMeasureCommand(self, start, measureIndex)
                self.addCommand(command)
            self.endMacro()

    @delayCall
    def insertMeasures(self, np):
        if len(self.measureClipboard) > 0:
            command = InsertAndPasteMeasures(self, np, self.measureClipboard)
            self.clearDragSelection()
            self.addCommand(command)

    @delayCall
    def pasteMeasuresOver(self, repeating = False):
        if len(self.measureClipboard) == 0 or not self.hasDragSelection():
            return
        start = self._dragSelection.start
        measureIndex = self._score.measurePositionToIndex(start)
        sourceLength = len(self.measureClipboard)
        targetLength = len(list(self.iterDragSelection()))
        self.clearDragSelection()
        self.beginMacro("paste over measures")
        measureCount = 0
        clearPositions = []
        if repeating:
            resultLength = targetLength
            while measureCount < resultLength:
                position = self.score.measureIndexToPosition(measureIndex
                                                         + measureCount)
                clearPositions.append(position)
                measureCount += 1
            command = ClearMeasureCommand(self, clearPositions)
            self.addCommand(command)
            measureData = []
            while len(measureData) < resultLength:
                measureData.extend(self.measureClipboard)
            measureData = measureData[:targetLength]
        else:
            resultLength = min([sourceLength, targetLength])
            while measureCount < resultLength:
                position = self.score.measureIndexToPosition(measureIndex
                                                         + measureCount)
                clearPositions.append(position)
                measureCount += 1
            command = ClearMeasureCommand(self, clearPositions)
            self.addCommand(command)
            while measureCount < targetLength:
                deletePosition = self.score.measureIndexToPosition(measureIndex
                                                               + sourceLength)
                command = DeleteMeasureCommand(self, deletePosition,
                                               measureIndex + sourceLength)
                self.addCommand(command)
                measureCount += 1
            if measureCount < sourceLength:
                position = self.score.measureIndexToPosition(measureIndex
                                                         + measureCount)
                command = InsertMeasuresCommand(self,
                                                position,
                                                sourceLength - measureCount,
                                                self.defaultCount, True)
                self.addCommand(command)
            measureData = self.measureClipboard
            measureData = measureData[:sourceLength]
        command = PasteMeasuresCommand(self, start, measureData)
        self.addCommand(command)
        self.endMacro()

    def loadScore(self, filename, quiet = False):
        try:
            newScore = ScoreSerializer.loadScore(filename)
        except DBErrors.DbReadError, exc:
            if not quiet:
                msg = "Error loading DrumBurp file %s" % filename
                QtGui.QMessageBox.warning(self.parent(),
                                          "Score load error",
                                          msg + "\n" + unicode(exc))
            return False
        except Exception, exc:
            raise
Exemple #4
0
class QScore(QtGui.QGraphicsScene):
    def __init__(self, parent):
        super(QScore, self).__init__(parent)
        self._scale = 1
        self._qStaffs = []
        self._qSections = []
        self._properties = parent.songProperties
        self._score = None
        self._dirty = None
        self._currentKey = None
        self._ignoreNext = False
        self.measureClipboard = []
        self._playingMeasure = None
        self._nextMeasure = None
        self._dragSelection = DragSelection(self)
        self._saved = False
        self._undoStack = QtGui.QUndoStack(self)
        self._inMacro = False
        self._macroCanReformat = False
        self._undoStack.canUndoChanged.connect(self.canUndoChanged)
        self._undoStack.undoTextChanged.connect(self.undoTextChanged)
        self._undoStack.canRedoChanged.connect(self.canRedoChanged)
        self._undoStack.redoTextChanged.connect(self.redoTextChanged)
        self._metaData = QMetaData(self)
        self._metaData.setPos(self.xMargins, self.yMargins)
        self._metaData.setVisible(self._properties.metadataVisible)
        self.metadataChanged.connect(lambda n_, v_: self.reBuild())
        self.paperSizeChanged.connect(parent.setPaperSize)
        self._kitData = QKitData(self)
        self._kitData.setPos(self.xMargins, 0)
        self._kitData.setVisible(self._properties.kitDataVisible)
        if parent.filename is not None:
            if not self.loadScore(parent.filename, quiet=False):
                parent.filename = None
                self.newScore(None)
        else:
            self.newScore(None)
        self.defaultCountChanged.connect(parent.setDefaultCount)
        self.spacingChanged.connect(parent.setSystemSpacing)
        self.sectionsChanged.connect(parent.setSections)
        self._properties.connectScore(self)
        self._potentials = []
        self._shortcutMemo = _HeadShortcutsMap(self._score.drumKit)
        self._stateMachine = DBStateMachine(Waiting, self)

    canUndoChanged = QtCore.pyqtSignal(bool)
    canRedoChanged = QtCore.pyqtSignal(bool)
    undoTextChanged = QtCore.pyqtSignal(str)
    redoTextChanged = QtCore.pyqtSignal(str)
    metadataChanged = QtCore.pyqtSignal(str, object)
    paperSizeChanged = QtCore.pyqtSignal(str)
    defaultCountChanged = QtCore.pyqtSignal(object)
    spacingChanged = QtCore.pyqtSignal(int)
    sectionsChanged = QtCore.pyqtSignal()
    showItem = QtCore.pyqtSignal(QGraphicsItem)
    dragHighlight = QtCore.pyqtSignal(bool)
    sceneFormatted = QtCore.pyqtSignal()
    playing = QtCore.pyqtSignal(bool)
    currentHeadsChanged = QtCore.pyqtSignal(QtCore.QString)
    statusMessageSet = QtCore.pyqtSignal(QtCore.QString)
    lilysizeChanged = QtCore.pyqtSignal(int)
    lilypagesChanged = QtCore.pyqtSignal(int)
    lilyFillChanged = QtCore.pyqtSignal(bool)
    lilyFormatChanged = QtCore.pyqtSignal(int)
    widthChanged = QtCore.pyqtSignal(int)

    def addCommand(self, command):
        if not self._inMacro:
            self._undoStack.beginMacro(command.description)
            self._undoStack.push(CheckUndo(self))
            if command.canReformat:
                self._addSaveStateCommand()
        self._undoStack.push(command)
        if not self._inMacro:
            if command.canReformat:
                self._addCheckStateCommand()
            self._undoStack.endMacro()
        self.dirty = not (self._undoStack.isClean() and self._saved)

    def addRepeatedCommand(self, name, command, arguments):
        self.beginMacro(name, command.canReformat)
        for args in arguments:
            self.addCommand(command(self, *args))
        self.endMacro()

    def beginMacro(self, name, canReformat=True):
        self._undoStack.beginMacro(name)
        self._inMacro = True
        self._undoStack.push(CheckUndo(self))
        self._macroCanReformat = canReformat
        if canReformat:
            self._addSaveStateCommand()

    def endMacro(self):
        if self._macroCanReformat:
            self._addCheckStateCommand()
        self._undoStack.endMacro()
        self._inMacro = False

    def _addSaveStateCommand(self):
        command = SaveFormatStateCommand(self)
        self._undoStack.push(command)

    def _addCheckStateCommand(self):
        command = CheckFormatStateCommand(self)
        self._undoStack.push(command)

    def undo(self):
        self._undoStack.undo()
        self.dirty = not (self._undoStack.isClean() and self._saved)

    def redo(self):
        self._undoStack.redo()
        self.dirty = not (self._undoStack.isClean() and self._saved)

    def startUp(self):
        self.metadataChanged.emit("width", self.scoreWidth)
        self.setCurrentHeads(None)

    def _getscoreWidth(self):
        if self._score is not None:
            return self._score.scoreData.width
        else:
            return None

    def _setscoreWidth(self, value):
        if self._score is None:
            return
        if self.scoreWidth != value:
            command = ScoreWidthCommand(self, value)
            self.clearDragSelection()
            self.addCommand(command)

    scoreWidth = property(fget=_getscoreWidth, fset=_setscoreWidth)

    artist = _metaDataProperty("artist")
    artistVisible = _metaDataProperty("artistVisible")
    creator = _metaDataProperty("creator")
    creatorVisible = _metaDataProperty("creatorVisible")
    title = _metaDataProperty("title")
    bpm = _metaDataProperty("bpm")
    bpmVisible = _metaDataProperty("bpmVisible")
    swing = _metaDataProperty("swing")

    @property
    def displayProperties(self):
        return self._properties

    def _getdirty(self):
        return self._dirty

    def _setdirty(self, value):
        if self._dirty != value:
            self._dirty = value
            self.dirtySignal.emit(self._dirty)

    dirty = property(fget=_getdirty, fset=_setdirty)
    dirtySignal = QtCore.pyqtSignal(bool)

    @property
    def kitSize(self):
        return len(self._score.drumKit)

    @property
    def lineOffsets(self):
        yOffsets = [
            drumIndex * self.ySpacing for drumIndex in xrange(self.kitSize)
        ]
        yOffsets.reverse()
        return yOffsets

    def _setScore(self, score):
        if score != self._score:
            score.formatScore(None)
            self._score = score
            self._shortcutMemo = _HeadShortcutsMap(score.drumKit)
            if score is not None:
                self.startUp()
            self._score.setCallBack(self.dataChanged)
            self._properties.lineSpacing = self._score.systemSpacing - 101
            self.paperSizeChanged.emit(self._score.paperSize)
            self.defaultCountChanged.emit(self._score.defaultCount)
            self.spacingChanged.emit(self._score.systemSpacing)
            self.lilysizeChanged.emit(self._score.lilysize)
            self.lilypagesChanged.emit(self._score.lilypages)
            self.lilyFillChanged.emit(self._score.lilyFill)
            self.lilyFormatChanged.emit(self._score.lilyFormat)
            self.sectionsChanged.emit()
            self._properties.newScore(self)
            self._kitData.setVisible(self._properties.kitDataVisible)
            self._metaData.setVisible(self._properties.metadataVisible)
            self.widthChanged.emit(self.scoreWidth)
            self._build()
            DBMidi.setKit(score.drumKit)
            self._undoStack.clear()
            self._undoStack.setClean()
            self._inMacro = False
            self.reBuild()
            self.dirty = False
            self._stateMachine = DBStateMachine(Waiting, self)
            self.scoreDisplayChanged.emit()

    @property
    def score(self):
        return self._score

    def _getscale(self):
        return self._scale

    def _setscale(self, value):
        if self._scale != value:
            self._scale = value
            yOffset = self.yMargins
            self._metaData.setPos(self.xMargins, yOffset)
            if self._properties.metadataVisible:
                yOffset += self._metaData.boundingRect().height()
            self._kitData.setPos(self.xMargins, yOffset)
            self._build()
            self.invalidate()

    scale = property(fget=_getscale, fset=_setscale)

    @property
    def xSpacing(self):
        return self._properties.xSpacing * self.scale

    @property
    def ySpacing(self):
        return self._properties.ySpacing * self.scale

    @property
    def lineSpacing(self):
        return self._properties.lineSpacing * self.scale

    @property
    def xMargins(self):
        return self._properties.xMargins * self.scale

    @property
    def yMargins(self):
        return self._properties.yMargins * self.scale

    def _build(self):
        self._clearStaffs()
        for staff in self._score.iterStaffs():
            self._addStaff(staff)
        for title in self._score.iterSections():
            self._addSection(title)
        self.placeStaffs()
        self.invalidate()

    scoreDisplayChanged = QtCore.pyqtSignal()

    @delayCall
    def reBuild(self, afterwards=None):
        if self._score.formatScore(None):
            self.scoreDisplayChanged.emit()
        self._build()
        if afterwards:
            afterwards()

    def checkFormatting(self):
        if self._score.formatScore(None):
            self.scoreDisplayChanged.emit()
            self.reBuild()

    def __iter__(self):
        return iter(self._qStaffs)

    def _clearStaffs(self):
        for qStaff in self._qStaffs:
            self.removeItem(qStaff)
        self._qStaffs = []
        for qSection in self._qSections:
            self.removeItem(qSection)
        self._qSections = []

    def _addStaff(self, staff):
        qStaff = QStaff(staff, len(self._qStaffs), self)
        self._qStaffs.append(qStaff)

    def _addSection(self, title):
        qSection = QSection(title, qScore=self)
        qSection.setIndex(len(self._qSections))
        self._qSections.append(qSection)

    def setSectionTitle(self, index, title):
        qSection = self._qSections[index]
        qSection.setTitle(title)

    def setPaperSize(self, newPaperSize):
        if newPaperSize != self._score.paperSize:
            command = SetPaperSizeCommand(self, newPaperSize)
            self.addCommand(command)

    def getQSection(self, sectionIndex):
        if 0 <= sectionIndex < len(self._qSections):
            return self._qSections[sectionIndex]

    def _getdefaultCount(self):
        return self._score.defaultCount

    def _setdefaultCount(self, newCount):
        if newCount != self._score.defaultCount:
            command = SetDefaultCountCommand(self, newCount)
            self.addCommand(command)

    defaultCount = property(_getdefaultCount, _setdefaultCount)

    def _getsystemSpacing(self):
        return self._score.systemSpacing

    def _setsystemSpacing(self, value):
        if self._score.systemSpacing != value:
            command = SetSystemSpacingCommand(self, value)
            self.addCommand(command)

    systemSpacing = property(_getsystemSpacing, _setsystemSpacing)

    def placeStaffs(self, staffCall=QStaff.placeMeasures):
        xMargins = self.xMargins
        yMargins = self.yMargins
        lineSpacing = self.lineSpacing
        yOffset = self.yMargins
        if self._properties.metadataVisible:
            yOffset += self._metaData.boundingRect().height()
        if self._properties.kitDataVisible:
            self._kitData.setY(yOffset)
            yOffset += self._kitData.boundingRect().height()
        newSection = True
        sectionIndex = 0
        maxWidth = 0
        for qStaff in self:
            if newSection:
                newSection = False
                if sectionIndex < len(self._qSections):
                    qSection = self._qSections[sectionIndex]
                    sectionIndex += 1
                    qSection.setPos(xMargins, yOffset)
                    yOffset += qSection.boundingRect().height()
            newSection = qStaff.isSectionEnd()
            qStaff.setPos(xMargins, yOffset)
            if staffCall is not None:
                staffCall(qStaff)
            yOffset += qStaff.height() + lineSpacing
            maxWidth = max(maxWidth, qStaff.width())
            newSection = qStaff.isSectionEnd()
        self.setSceneRect(0, 0, maxWidth + 2 * xMargins,
                          yOffset - lineSpacing + yMargins)
        self.sceneFormatted.emit()

    def xSpacingChanged(self):
        self.placeStaffs(QStaff.xSpacingChanged)

    def ySpacingChanged(self):
        self.placeStaffs(QStaff.ySpacingChanged)

    def lineSpacingChanged(self):
        self.placeStaffs(None)

    def sectionFontChanged(self):
        self._metaData.fontChanged()
        for qsection in self._qSections:
            qsection.setFont(self._properties.sectionFont)
        self.lineSpacingChanged()

    def metadataVisibilityChanged(self):
        self.reBuild()
        self._metaData.setVisible(self._properties.metadataVisible)

    def metadataFontChanged(self):
        with self.metaChange():
            self._metaData.fontChanged()

    def noteFontChanged(self):
        with self.kitDataChange():
            self._kitData.fontChanged()

    def kitDataVisibleChanged(self):
        self.reBuild()
        self._kitData.setVisible(self._properties.kitDataVisible)

    def dataChanged(self, notePosition):
        staff = self._qStaffs[notePosition.staffIndex]
        staff.dataChanged(notePosition)
        self.scoreDisplayChanged.emit()

    def ignoreNextClick(self):
        self._ignoreNext = True

    def mousePressEvent(self, event):
        item = self.itemAt(event.scenePos())
        if not isinstance(item, QMeasure):
            self.clearDragSelection()
        event.ignore()
        if self._ignoreNext:
            self._ignoreNext = False
        else:
            super(QScore, self).mousePressEvent(event)

    def keyPressEvent(self, event):
        if not event.isAutoRepeat():
            if event.key() == QtCore.Qt.Key_Escape:
                self.sendFsmEvent(Escape())
            else:
                if self._currentKey == None and event.text():
                    self._currentKey = unicode(event.text())
                    self._highlightCurrentKeyHead()
                    self.update()
        return super(QScore, self).keyPressEvent(event)

    def keyReleaseEvent(self, event):
        if not event.isAutoRepeat():
            if event.key() != QtCore.Qt.Key_Escape:
                if unicode(event.text()) == self._currentKey:
                    self._currentKey = None
                    self._highlightCurrentKeyHead()
                    self.update()
        return super(QScore, self).keyReleaseEvent(event)

    def getCurrentHead(self):
        return self._shortcutMemo.getCurrentHead(self._currentKey)

    def setCurrentHeads(self, drumIndex):
        self._shortcutMemo.setDrumIndex(drumIndex)
        self._highlightCurrentKeyHead()

    def _highlightCurrentKeyHead(self):
        headText = self._shortcutMemo.getShortcutText(self._currentKey)
        self.currentHeadsChanged.emit(QtCore.QString(headText))

    def copyMeasures(self, np=None):
        if np is not None:
            self.measureClipboard = [self._score.copyMeasure(np)]
        elif self.hasDragSelection():
            self.measureClipboard = [
                self._score.copyMeasure(dragNP)
                for (unusedMeasure, unusedIndex,
                     dragNP) in self.iterDragSelection()
            ]

    def clearMeasures(self, np=None):
        if np is not None:
            command = ClearMeasureCommand(self, [np])
        else:
            command = ClearMeasureCommand(self, [
                dragNP for (unusedMeasure, unusedIndex,
                            dragNP) in self.iterDragSelection()
            ])
        self.addCommand(command)

    def deleteMeasures(self, np=None):
        if np is not None:
            command = DeleteMeasureCommand(self, np)
            self.clearDragSelection()
            self.addCommand(command)
        else:
            if not self.hasDragSelection():
                return
            start = self._dragSelection.start
            measureIndex = self._score.measurePositionToIndex(start)
            measures = list(self.iterDragSelection())
            self.clearDragSelection()
            self.beginMacro("delete measures")
            for unused in measures:
                command = DeleteMeasureCommand(self, start, measureIndex)
                self.addCommand(command)
            self.endMacro()

    @delayCall
    def insertMeasures(self, np):
        if len(self.measureClipboard) > 0:
            command = InsertAndPasteMeasures(self, np, self.measureClipboard)
            self.clearDragSelection()
            self.addCommand(command)

    @delayCall
    def pasteMeasuresOver(self, repeating=False):
        if len(self.measureClipboard) == 0 or not self.hasDragSelection():
            return
        start = self._dragSelection.start
        measureIndex = self._score.measurePositionToIndex(start)
        sourceLength = len(self.measureClipboard)
        targetLength = len(list(self.iterDragSelection()))
        self.clearDragSelection()
        self.beginMacro("paste over measures")
        measureCount = 0
        clearPositions = []
        if repeating:
            resultLength = targetLength
            while measureCount < resultLength:
                position = self.score.measureIndexToPosition(measureIndex +
                                                             measureCount)
                clearPositions.append(position)
                measureCount += 1
            command = ClearMeasureCommand(self, clearPositions)
            self.addCommand(command)
            measureData = []
            while len(measureData) < resultLength:
                measureData.extend(self.measureClipboard)
            measureData = measureData[:targetLength]
        else:
            resultLength = min([sourceLength, targetLength])
            while measureCount < resultLength:
                position = self.score.measureIndexToPosition(measureIndex +
                                                             measureCount)
                clearPositions.append(position)
                measureCount += 1
            command = ClearMeasureCommand(self, clearPositions)
            self.addCommand(command)
            while measureCount < targetLength:
                deletePosition = self.score.measureIndexToPosition(
                    measureIndex + sourceLength)
                command = DeleteMeasureCommand(self, deletePosition,
                                               measureIndex + sourceLength)
                self.addCommand(command)
                measureCount += 1
            if measureCount < sourceLength:
                position = self.score.measureIndexToPosition(measureIndex +
                                                             measureCount)
                command = InsertMeasuresCommand(self, position,
                                                sourceLength - measureCount,
                                                self.defaultCount, True)
                self.addCommand(command)
            measureData = self.measureClipboard
            measureData = measureData[:sourceLength]
        command = PasteMeasuresCommand(self, start, measureData)
        self.addCommand(command)
        self.endMacro()

    def loadScore(self, filename, quiet=False):
        try:
            newScore = ScoreSerializer.loadScore(filename)
        except DBErrors.DbReadError, exc:
            if not quiet:
                msg = "Error loading DrumBurp file %s" % filename
                QtGui.QMessageBox.warning(self.parent(), "Score load error",
                                          msg + "\n" + unicode(exc))
            return False
        except Exception, exc:
            raise