You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
621 lines
31 KiB
621 lines
31 KiB
#! /usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
Copyright 2022, 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")
|
|
|
|
#Standard Library Modules
|
|
import os.path
|
|
from pathlib import Path
|
|
|
|
#Third Party Modules
|
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
|
translate = QtCore.QCoreApplication.translate
|
|
|
|
#Template Modules
|
|
|
|
|
|
#Our modules
|
|
import engine.api as api
|
|
from engine.midiinput.stepmidiinput import stepMidiInput #singleton instance
|
|
from .constantsAndConfigs import constantsAndConfigs
|
|
from .submenus import SecondaryClefMenu, SecondaryKeySignatureMenu, SecondaryDynamicsMenu, SecondaryMetricalInstructionMenu, SecondaryTempoChangeMenu, SecondaryTemporaryTempoChangeMenu, SecondarySplitMenu, TransposeMenu, pedalNoteChooser, SecondaryProperties, SecondaryProgramChangeMenu, SecondaryChannelChangeMenu, ChooseOne, forwardText
|
|
|
|
class ModalKeys(object):
|
|
def __init__(self):
|
|
pass
|
|
|
|
def keyRest(self):
|
|
if stepMidiInput.midiInIsActive:
|
|
baseDuration = api.session.data.cursor.prevailingBaseDuration
|
|
api.insertRest(baseDuration)
|
|
#else: deactivated.
|
|
|
|
def key1(self):
|
|
if stepMidiInput.midiInIsActive: api.prevailing1()
|
|
else: api.insert1()
|
|
|
|
def key2(self):
|
|
if stepMidiInput.midiInIsActive: api.prevailing2()
|
|
else: api.insert2()
|
|
|
|
def key3(self):
|
|
if stepMidiInput.midiInIsActive: api.prevailing4()
|
|
else: api.insert4()
|
|
|
|
def key4(self):
|
|
if stepMidiInput.midiInIsActive: api.prevailing8()
|
|
else: api.insert8()
|
|
|
|
def key5(self):
|
|
if stepMidiInput.midiInIsActive: api.prevailing16()
|
|
else: api.insert16()
|
|
|
|
def key6(self):
|
|
if stepMidiInput.midiInIsActive: api.prevailing32()
|
|
else: api.insert32()
|
|
|
|
def key7(self):
|
|
if stepMidiInput.midiInIsActive: api.prevailingBrevis()
|
|
else: api.insertBrevis()
|
|
|
|
def key8(self):
|
|
if stepMidiInput.midiInIsActive: api.prevailingLonga()
|
|
else: api.insertLonga()
|
|
|
|
def key9(self):
|
|
pass
|
|
|
|
def key0(self):
|
|
pass
|
|
|
|
class MenuActionDatabase(object):
|
|
"""There are different edit and view modes in Laborejo-Qt:
|
|
|
|
Main Program
|
|
ScoreView
|
|
NoteEdit
|
|
Noteheads
|
|
Rectangles
|
|
BlockEdit
|
|
Transparent Block Handles
|
|
CCEdit
|
|
CC 0
|
|
CC 1
|
|
...
|
|
CC 127
|
|
DataEditor
|
|
|
|
|
|
Each level has shorcuts associated with them which depend on the context.
|
|
To implement those we use the qt shortcut contexts. See QAction docs.
|
|
|
|
Additionaly there is midi-in and the modal number keys, which are
|
|
connected to each other. Without midi in the modal keys insert notes,
|
|
in midi mode they choose the duration for the next midi key.
|
|
"""
|
|
|
|
def __init__(self, mainWindow):
|
|
self.mainWindow = mainWindow
|
|
self.modalKeys = ModalKeys()
|
|
|
|
modes = QtWidgets.QActionGroup(self.mainWindow)
|
|
modes.addAction(self.mainWindow.ui.actionNotation_Mode)
|
|
modes.addAction(self.mainWindow.ui.actionBlock_Mode)
|
|
modes.addAction(self.mainWindow.ui.actionCC_Mode)
|
|
self.mainWindow.ui.actionNotation_Mode.setChecked(True)
|
|
|
|
self.rememberMidi = stepMidiInput.midiInIsActive
|
|
|
|
api.callbacks.historyChanged.append(self.renameUndoRedoByHistory)
|
|
api.callbacks.metronomeChanged.append(lambda v: self.mainWindow.ui.actionMetronome_Enabled.setChecked(v["enabled"])) #accompanied by a normal menu action down below
|
|
|
|
self.actionsWithoutMenu = { #these are only available in Note Edit Mode, not in CC Edit Mode etc. but have no menu entry.
|
|
#Of course these actions have shortcuts, they are in a menu, visible and editable in qt-Designer, which gets hidden on program start.
|
|
self.mainWindow.ui.actionLeft : api.left,
|
|
self.mainWindow.ui.actionRight : api.right,
|
|
self.mainWindow.ui.actionSelectLeft : api.selectLeft,
|
|
self.mainWindow.ui.actionSelectRight : api.selectRight,
|
|
|
|
self.mainWindow.ui.actionMeasureLeft : api.measureLeft,
|
|
self.mainWindow.ui.actionMeasureRight : api.measureRight,
|
|
self.mainWindow.ui.actionSelectMeasureLeft : api.selectMeasureLeft,
|
|
self.mainWindow.ui.actionSelectMeasureRight : api.selectMeasureRight,
|
|
|
|
|
|
self.mainWindow.ui.actionBlockLeft : api.blockLeft,
|
|
self.mainWindow.ui.actionBlockRight : api.blockRight,
|
|
self.mainWindow.ui.actionSelectBlockLeft : api.selectBlockLeft,
|
|
self.mainWindow.ui.actionSelectBlockRight : api.selectBlockRight,
|
|
|
|
self.mainWindow.ui.actionHead : api.head,
|
|
self.mainWindow.ui.actionTail : api.tail,
|
|
self.mainWindow.ui.actionSelectHead : api.selectHead,
|
|
self.mainWindow.ui.actionSelectTail : api.selectTail,
|
|
|
|
self.mainWindow.ui.actionTrackUp : api.trackUp,
|
|
self.mainWindow.ui.actionTrackDown : api.trackDown,
|
|
self.mainWindow.ui.actionTrackFirst : api.trackFirst,
|
|
self.mainWindow.ui.actionTrackLast : api.trackLast,
|
|
self.mainWindow.ui.actionSelectTrackUp : api.selectTrackUp,
|
|
self.mainWindow.ui.actionSelectTrackDown : api.selectTrackDown,
|
|
self.mainWindow.ui.actionSelectTrackFirst : api.selectTrackFirst,
|
|
self.mainWindow.ui.actionSelectTrackLast : api.selectTrackLast,
|
|
|
|
self.mainWindow.ui.actionUp : api.up,
|
|
self.mainWindow.ui.actionDown : api.down,
|
|
self.mainWindow.ui.actionUpOctave : api.upOctave,
|
|
self.mainWindow.ui.actionDownOctave : api.downOctave,
|
|
self.mainWindow.ui.actionSelectUp : api.up, #these are the same as up and down. But for a good user experience they should work if shift is pressed down as well.
|
|
self.mainWindow.ui.actionSelectDown : api.down,
|
|
self.mainWindow.ui.actionSelectUpOctave : api.upOctave,
|
|
self.mainWindow.ui.actionSelectDownOctave : api.downOctave,
|
|
|
|
|
|
self.mainWindow.ui.actionSelectAllTracks : api.selectAllTracks,
|
|
self.mainWindow.ui.actionSelectTrack : api.selectTrack,
|
|
self.mainWindow.ui.actionSelectMeasureColumn : api.selectMeasureColumn,
|
|
|
|
#CC Edit Actions. Share the same shortcuts as some of the actions above. But only one of these is enabled at any time.
|
|
#Don't forget to add them to the list below: self.ccEditActions
|
|
#...
|
|
}
|
|
|
|
|
|
self.actions = { #always available in the main window. It doesn't matter if you change to CC mode or Note Edit etc.
|
|
self.mainWindow.ui.actionToggle_Notehead_Rectangles : self.mainWindow.scoreView.toggleNoteheadsRectangles,
|
|
self.mainWindow.ui.actionData_Editor : self.mainWindow.toggleMainView,
|
|
self.mainWindow.ui.actionProperties : lambda: SecondaryProperties(self.mainWindow),
|
|
self.mainWindow.ui.actionFollow_Playhead : self.mainWindow.scoreView.toggleFollowPlayhead,
|
|
#Modes Submenu
|
|
self.mainWindow.ui.actionNotation_Mode : self.mainWindow.scoreView.updateMode,
|
|
self.mainWindow.ui.actionBlock_Mode : self.mainWindow.scoreView.updateMode,
|
|
self.mainWindow.ui.actionCC_Mode : self.mainWindow.scoreView.updateMode,
|
|
|
|
self.mainWindow.ui.actionChange_Grid_Rhythm : self.mainWindow.scoreView.changeGridRhythm,
|
|
self.mainWindow.ui.actionAdd_Track : api.newEmptyTrack,
|
|
self.mainWindow.ui.actionDelete_Current_Track : api.deleteCurrentTrack,
|
|
self.mainWindow.ui.actionUse_Current_Track_as_Metronome : api.useCurrentTrackAsMetronome,
|
|
self.mainWindow.ui.actionMidi_In_is_Active : self.toggleMidiInIsActive,
|
|
self.mainWindow.ui.actionZoom_In_Score_View : self.mainWindow.zoomIn,
|
|
self.mainWindow.ui.actionZoom_Out_Score_View : self.mainWindow.zoomOut,
|
|
self.mainWindow.ui.actionWiden_Score_View : self.mainWindow.widen,
|
|
self.mainWindow.ui.actionShrink_Score_View : self.mainWindow.shrinken,
|
|
self.mainWindow.ui.actionSave : api.save,
|
|
self.mainWindow.ui.actionQuit : self.mainWindow.nsmClient.serverSendExitToSelf,
|
|
|
|
self.mainWindow.ui.actionShow_PDF : api.showPDF,
|
|
self.mainWindow.ui.actionExport_to_Ly : self.exportLy,
|
|
|
|
self.mainWindow.ui.actionPlayFromBeginning : api.playFromStart,
|
|
self.mainWindow.ui.actionPlayPause : api.playPause,
|
|
self.mainWindow.ui.actionPlayFromEditCursor : api.playFromCursor,
|
|
self.mainWindow.ui.actionPlay_from_Block : api.playFromBlockStart,
|
|
self.mainWindow.ui.actionMetronome_Enabled : api.toggleMetronome, #toggle is enough. The callback makes sure all the checkboxes have the correct value.
|
|
}
|
|
|
|
self.noteEditActions = { #these are only available in Note Edit Mode, not in CC Edit Mode etc. #all xxxEditActions are mutually exclusive.
|
|
self.mainWindow.ui.actionBeam : api.toggleBeam,
|
|
self.mainWindow.ui.actionRemoveBeams : api.removeBeam,
|
|
|
|
self.mainWindow.ui.actionLegatoSlur : api.insertLegatoSlur,
|
|
self.mainWindow.ui.actionClef : SecondaryClefMenu(self.mainWindow), #no lambda for submenus. They get created here once and have a __call__ option that executes them. There is no internal state in these menus.
|
|
self.mainWindow.ui.actionKey_Signature : SecondaryKeySignatureMenu(self.mainWindow),
|
|
self.mainWindow.ui.actionDynamics : SecondaryDynamicsMenu(self.mainWindow),
|
|
self.mainWindow.ui.actionMulti_Measure_Rest : lambda: api.insertMultiMeasureRest(1),
|
|
self.mainWindow.ui.actionMetrical_Instruction : SecondaryMetricalInstructionMenu(self.mainWindow),
|
|
|
|
|
|
self.mainWindow.ui.actionTempo_Change : lambda: SecondaryTempoChangeMenu(self.mainWindow),
|
|
self.mainWindow.ui.actionDelete_Tempo_Change : api.removeCurrentTempoItem,
|
|
self.mainWindow.ui.actionTemporary_Tempo_Change : SecondaryTemporaryTempoChangeMenu(self.mainWindow),
|
|
|
|
self.mainWindow.ui.actionSharpen_Note : api.sharpenNote,
|
|
self.mainWindow.ui.actionFlatten_Note : api.flattenNote,
|
|
self.mainWindow.ui.actionStep_Up : api.stepUp,
|
|
self.mainWindow.ui.actionStep_Down : api.stepDown,
|
|
self.mainWindow.ui.actionOctave_Up : api.stepUpOctave,
|
|
self.mainWindow.ui.actionOctave_Down : api.stepDownOctave,
|
|
self.mainWindow.ui.actionTransposeChord : lambda: TransposeMenu(self.mainWindow, "item"),
|
|
|
|
|
|
self.mainWindow.ui.actionVelocityMore : lambda: self.mainWindow.scoreView._switchToRectanglesForFunction(api.moreVelocity),
|
|
self.mainWindow.ui.actionVelocityLess : lambda: self.mainWindow.scoreView._switchToRectanglesForFunction(api.lessVelocity),
|
|
self.mainWindow.ui.actionDurationModMore : lambda: self.mainWindow.scoreView._switchToRectanglesForFunction(api.moreDuration),
|
|
self.mainWindow.ui.actionDurationModLess : lambda: self.mainWindow.scoreView._switchToRectanglesForFunction(api.lessDuration),
|
|
self.mainWindow.ui.actionReset_Velocity_Duration_Mod : lambda: self.mainWindow.scoreView._switchToRectanglesForFunction(api.resetDurationVelocity),
|
|
|
|
self.mainWindow.ui.actionAugment : api.augment,
|
|
self.mainWindow.ui.actionDiminish : api.diminish,
|
|
self.mainWindow.ui.actionDots : api.dot,
|
|
self.mainWindow.ui.actionTriplet : api.triplet,
|
|
#self.mainWindow.ui.actionCustomTuplet : something with a GUI that allows any type and number of nested tuplets, #TODO
|
|
self.mainWindow.ui.actionStaccato : api.staccato,
|
|
self.mainWindow.ui.actionTenuto : api.tenuto,
|
|
self.mainWindow.ui.actionTie : api.tie,
|
|
self.mainWindow.ui.actionSplit_in_2 : lambda: api.split(2),
|
|
self.mainWindow.ui.actionSplit_in_3 : lambda: api.split(3),
|
|
self.mainWindow.ui.actionCustom_Split : SecondarySplitMenu(self.mainWindow), #no lambda for submenus. They get created here once and have a __call__ option that executes them. There is no internal state in these menus.
|
|
|
|
self.mainWindow.ui.actionMIDI_Channel_Plus : api.midiRelativeChannelPlus,
|
|
self.mainWindow.ui.actionMIDI_Channel_Minus : api.midiRelativeChannelMinus,
|
|
self.mainWindow.ui.actionMIDI_Channel_Reset : api.midiRelativeChannelReset,
|
|
|
|
self.mainWindow.ui.actionAppend_Block : api.appendBlock,
|
|
self.mainWindow.ui.actionSplit_Current_Block : api.splitBlock,
|
|
self.mainWindow.ui.actionJoin_with_next_Block : api.joinBlock,
|
|
self.mainWindow.ui.actionDuplicate : api.duplicateCurrentBlock,
|
|
self.mainWindow.ui.actionCreate_Linked_Copy : api.duplicateContentLinkCurrentBlock,
|
|
self.mainWindow.ui.actionUnlink_Current_Block : api.unlinkCurrentBlock,
|
|
self.mainWindow.ui.actionDelete_Current_Block : api.deleteCurrentBlock,
|
|
self.mainWindow.ui.actionMove_Block_to_start_of_track : api.moveCurrentBlockToStartOfTrack,
|
|
self.mainWindow.ui.actionMove_Block_to_end_of_track : api.moveCurrentBlockToEndOfTrack,
|
|
|
|
self.mainWindow.ui.actionTranspose_Score : lambda: TransposeMenu(self.mainWindow, "score"),
|
|
self.mainWindow.ui.actionDelete_All_Empty_Blocks : api.deleteEmptyBlocks,
|
|
|
|
self.mainWindow.ui.actionDelete : api.delete,
|
|
self.mainWindow.ui.actionBackspace : api.backspace,
|
|
self.mainWindow.ui.actionAddCursorNoteToChord : api.addCursorNoteToChord,
|
|
self.mainWindow.ui.actionDeleteCursorNoteFromChord : api.deleteCursorNoteFromChord,
|
|
self.mainWindow.ui.actionCut : api.cutObjects,
|
|
self.mainWindow.ui.actionCopy : api.copyObjects,
|
|
self.mainWindow.ui.actionPaste : api.pasteObjects,
|
|
self.mainWindow.ui.actionDuplicateItem : api.duplicate,
|
|
self.mainWindow.ui.actionUndo : api.undo,
|
|
self.mainWindow.ui.actionRedo : api.redo,
|
|
|
|
self.mainWindow.ui.actionRedrawAllTracks : api.updateCallbackAllTracks,
|
|
|
|
#Toolbox
|
|
#Note Generation
|
|
self.mainWindow.ui.actionPedalNotes : lambda: pedalNoteChooser(self.mainWindow),
|
|
self.mainWindow.ui.actionRandom_chromatic_in_clef_range: api.insertRandomChromaticInClefRange,
|
|
self.mainWindow.ui.actionRandom_in_scale_in_clef_range: api.insertRandomFromScaleInClefRange,
|
|
self.mainWindow.ui.actionRandom_pitch_from_clipboard: api.insertRandomFromClipboard,
|
|
self.mainWindow.ui.actionRandom_in_scale_in_octave_around_cursor: api.insertRandomFromScaleHypoModeCursor,
|
|
self.mainWindow.ui.actionRandom_in_scale_in_cursor_plus_octave: api.insertRandomFromScaleAuthenticModeCursor,
|
|
self.mainWindow.ui.actionMirror_around_Cursor: api.mirrorAroundCursor,
|
|
|
|
#Note Sorting
|
|
self.mainWindow.ui.actionRandom: api.reorderShuffle,
|
|
self.mainWindow.ui.actionReverse: api.reorderReverse,
|
|
self.mainWindow.ui.actionAscending: api.reorderAscending,
|
|
self.mainWindow.ui.actionDescending: api.reoderdDescending,
|
|
|
|
#Midi
|
|
self.mainWindow.ui.actionInstrument_Change: SecondaryProgramChangeMenu(self.mainWindow), #no lambda for submenus. They get created here once and have a __call__ option that executes them. There is no internal state in these menus.
|
|
self.mainWindow.ui.actionChannel_Change: SecondaryChannelChangeMenu(self.mainWindow), #no lambda for submenus. They get created here once and have a __call__ option that executes them. There is no internal state in these menus.
|
|
|
|
#Lilypond
|
|
#Print and Export is in self.actions
|
|
self.mainWindow.ui.actionLyBarline: ChooseOne(self.mainWindow, translate("menu", "Choose a Barline"), api.getLilypondBarlineList()),
|
|
self.mainWindow.ui.actionLyRepeat: ChooseOne(self.mainWindow, translate("menu", "Choose a Repeat"), api.getLilypondRepeatList()),
|
|
self.mainWindow.ui.actionLyFree_Instruction: lambda: forwardText(self.mainWindow, translate("menu", "Enter Instruction"), api.lilypondText),
|
|
self.mainWindow.ui.actionLyMarkAbove: lambda: forwardText(self.mainWindow, translate("menu", "Text above the Staff, will be attached to previous(!) item."), lambda tx: api.lilypondMark(tx, True)),
|
|
self.mainWindow.ui.actionLyMarkBelow: lambda: forwardText(self.mainWindow, translate("menu", "Text below the Staff, will be attached to previous(!) item."), lambda tx: api.lilypondMark(tx, False)),
|
|
}
|
|
|
|
self.modalActions = { #these are only available in Note Edit Mode, not in CC Edit Mode etc.
|
|
self.mainWindow.ui.actionModal1 : self.modalKeys.key1,
|
|
self.mainWindow.ui.actionModal2 : self.modalKeys.key2,
|
|
self.mainWindow.ui.actionModal3 : self.modalKeys.key3,
|
|
self.mainWindow.ui.actionModal4 : self.modalKeys.key4,
|
|
self.mainWindow.ui.actionModal5 : self.modalKeys.key5,
|
|
self.mainWindow.ui.actionModal6 : self.modalKeys.key6,
|
|
self.mainWindow.ui.actionModal7 : self.modalKeys.key7, #brevis
|
|
self.mainWindow.ui.actionModal8 : self.modalKeys.key8, #longa
|
|
|
|
self.mainWindow.ui.actionPrevailingRest : self.modalKeys.keyRest,
|
|
|
|
self.mainWindow.ui.actionShift_modal1 : api.insertRest1,
|
|
self.mainWindow.ui.actionShift_modal2 : api.insertRest2,
|
|
self.mainWindow.ui.actionShift_modal3 : api.insertRest4,
|
|
self.mainWindow.ui.actionShift_modal4 : api.insertRest8,
|
|
self.mainWindow.ui.actionShift_modal5 : api.insertRest16,
|
|
self.mainWindow.ui.actionShift_modal6 : api.insertRest32,
|
|
#self.mainWindow.ui.actionShift_modal7 : api.insertRestBrevis,
|
|
#self.mainWindow.ui.actionShift_modal8 : api.insertRestLonga,
|
|
#self.mainWindow.ui.actionShift_modal7 : api.insertRest64,
|
|
#self.mainWindow.ui.actionShift_modal8 : api.insertRest128,
|
|
}
|
|
|
|
#These are available in CC Edit Mode, Note Edit mode etc.
|
|
#Add to this list if you want the menu actions to always be enabled
|
|
self.nonEditingMusicActions = [
|
|
self.mainWindow.ui.actionPlayFromBeginning,
|
|
self.mainWindow.ui.actionPlayPause,
|
|
self.mainWindow.ui.actionPlayFromEditCursor,
|
|
self.mainWindow.ui.actionToggle_Notehead_Rectangles,
|
|
self.mainWindow.ui.actionData_Editor,
|
|
self.mainWindow.ui.actionFollow_Playhead,
|
|
self.mainWindow.ui.actionNotation_Mode,
|
|
self.mainWindow.ui.actionBlock_Mode,
|
|
self.mainWindow.ui.actionCC_Mode,
|
|
self.mainWindow.ui.actionChange_Grid_Rhythm,
|
|
self.mainWindow.ui.actionAdd_Track,
|
|
self.mainWindow.ui.actionUndo,
|
|
self.mainWindow.ui.actionRedo,
|
|
self.mainWindow.ui.actionZoom_In_Score_View,
|
|
self.mainWindow.ui.actionZoom_Out_Score_View,
|
|
self.mainWindow.ui.actionWiden_Score_View,
|
|
self.mainWindow.ui.actionShrink_Score_View,
|
|
self.mainWindow.ui.actionSave,
|
|
self.mainWindow.ui.actionQuit,
|
|
self.mainWindow.ui.actionShow_PDF,
|
|
self.mainWindow.ui.actionExport_to_Ly,
|
|
self.mainWindow.ui.actionMetronome_Enabled,
|
|
self.mainWindow.ui.actionProperties,
|
|
]
|
|
|
|
#only in CC edit, when editing user points. #all xxxEditActions are mutually exclusive.
|
|
#These are defined in non-menu actions above.
|
|
self.ccEditActions = [
|
|
# self.mainWindow.ui.CCactionDelete,
|
|
]
|
|
|
|
#Prepare non-designer widgets. Designer can't put normal widgets in a toolbar, but Qt can.
|
|
#Eventhough this is a dict, which has no order, the CREATION has an order. So the first item in the dict will be the first item in the toolBar
|
|
self.extraToolBarWidgets = {
|
|
"snapToGrid" : self.mainWindow.ui.toolBar.addWidget(ToolBarSnapToGrid(mainWindow=self.mainWindow)),
|
|
"metronome" : self.mainWindow.ui.toolBar.addWidget(ToolBarMetronome(mainWindow=self.mainWindow)),
|
|
"playbackSpeed" : self.mainWindow.ui.toolBar.addWidget(ToolBarPlaybackSpeed(mainWindow=self.mainWindow)),
|
|
"ccType" : self.mainWindow.ui.toolBar.addWidget(ToolBarCCType(mainWindow=self.mainWindow)), #keep this at the end of the toolbar because it toggles visibility
|
|
}
|
|
|
|
self.toolbarContexts = {
|
|
"notation" : [self.extraToolBarWidgets["snapToGrid"], self.extraToolBarWidgets["metronome"], self.extraToolBarWidgets["playbackSpeed"], ],
|
|
"cc" : [self.extraToolBarWidgets["snapToGrid"], self.extraToolBarWidgets["metronome"], self.extraToolBarWidgets["ccType"], self.extraToolBarWidgets["playbackSpeed"]],
|
|
"block": [self.extraToolBarWidgets["snapToGrid"], self.extraToolBarWidgets["metronome"], self.extraToolBarWidgets["playbackSpeed"], ],
|
|
}
|
|
|
|
#Now connect all actions to functions
|
|
|
|
for action, function in self.actions.items():
|
|
action.triggered.connect(function)
|
|
|
|
#all xxxEditActions are mutually exclusive.
|
|
for action, function in self.noteEditActions.items():
|
|
action.triggered.connect(function)
|
|
|
|
for action in self.ccEditActions: # a list.
|
|
action.setEnabled(False) #These are the same keys as noteEditActions, so we need to disable them for now.
|
|
|
|
for action, function in self.modalActions.items():
|
|
self.mainWindow.ui.centralwidget.addAction(action) #no actions without a widget
|
|
action.triggered.connect(function)
|
|
|
|
#Do not connect modalActionsPrevailing yet
|
|
|
|
for action, function in self.actionsWithoutMenu.items():
|
|
self.mainWindow.ui.centralwidget.addAction(action) #no actions without a widget
|
|
action.triggered.connect(function)
|
|
|
|
self.mainWindow.ui.menubar.removeAction(self.mainWindow.ui.menuGeneric.menuAction()) #thats why we added the actions to the centralwidget above.
|
|
self.loadToolbarContext("notation")
|
|
|
|
def toggleMidiInIsActive(self):
|
|
"""That is: "toggle midiInIsActive", not "toggleMidiIn is active?" """
|
|
stepMidiInput.toggleMidiIn()
|
|
self.mainWindow.ui.actionPrevailingRest.setEnabled(stepMidiInput.midiInIsActive)
|
|
self.mainWindow.ui.actionMidi_In_is_Active.setChecked(stepMidiInput.midiInIsActive)
|
|
|
|
def setMusicEditingEnabled(self, boolean):
|
|
"""This is the "reset to standard" functions for shortcuts.
|
|
If you want to enable other keys, such as CC editing keys,
|
|
you have to call another function after this one"""
|
|
allActions = list(self.actions.keys()) + list(self.modalActions.keys()) + list(self.actionsWithoutMenu.keys()) + list(self.noteEditActions.keys())
|
|
|
|
for x in self.nonEditingMusicActions:
|
|
allActions.remove(x)
|
|
|
|
for editAction in allActions:
|
|
editAction.setEnabled(boolean)
|
|
|
|
for action in self.ccEditActions: # Call this after enabling stuff in the for-loop above.
|
|
action.setEnabled(False) #These are the same keys as noteEditActions, so we need to disable them for now. Maybe they are enabled later again, after this function.
|
|
|
|
self.mainWindow.ui.actionPrevailingRest.setEnabled(boolean and stepMidiInput.midiInIsActive)
|
|
|
|
def noteEditMode(self):
|
|
self.setMusicEditingEnabled(True)
|
|
|
|
def ccEditMode(self):
|
|
self.setMusicEditingEnabled(False)
|
|
for action in self.ccEditActions: # a list.
|
|
action.setEnabled(True) #These are the same keys as noteEditActions, which are disabled above.
|
|
|
|
def writeProtection(self, boolean):
|
|
"""Artificial read only mode that was designed for the mouse-only track editor etc.
|
|
When zoomed out the user should be able to use the mouse but the cursor is hidden and
|
|
"""
|
|
|
|
if boolean:
|
|
#Switch off midi input
|
|
if stepMidiInput.midiInIsActive:
|
|
self.rememberMidi = stepMidiInput.midiInIsActive
|
|
stepMidiInput.toggleMidiIn()
|
|
assert not stepMidiInput.midiInIsActive
|
|
self.mainWindow.ui.actionPrevailingRest.setEnabled(stepMidiInput.midiInIsActive)
|
|
self.mainWindow.ui.actionMidi_In_is_Active.setChecked(stepMidiInput.midiInIsActive)
|
|
#Deactivate Note Editing Actions
|
|
self.setMusicEditingEnabled(False)
|
|
self.mainWindow.scoreView.scoreScene.cursor.hide() #the cursor which can be used to edit notes. Since the cursor shortcuts get disabled it cannot be changed invisbly.
|
|
else:
|
|
self.setMusicEditingEnabled(True)
|
|
self.mainWindow.scoreView.scoreScene.cursor.show()
|
|
if self.rememberMidi and not stepMidiInput.midiInIsActive:
|
|
self.toggleMidiInIsActive()
|
|
self.rememberMidi = None
|
|
|
|
def loadToolbarContext(self, nameAsString):
|
|
"""Hide all toolbar actions and only load those
|
|
which are in the current context, defined as list"""
|
|
|
|
assert nameAsString in constantsAndConfigs.availableEditModes
|
|
|
|
for action in self.mainWindow.ui.toolBar.actions():
|
|
action.setVisible(False)
|
|
|
|
for action in self.toolbarContexts[nameAsString]:
|
|
action.setVisible(True)
|
|
|
|
def renameUndoRedoByHistory(self, undoList, redoList):
|
|
if undoList:
|
|
self.mainWindow.ui.actionUndo.setText(translate("menu", "Undo: {}").format(undoList[-1]))
|
|
self.mainWindow.ui.actionUndo.setEnabled(True)
|
|
else:
|
|
self.mainWindow.ui.actionUndo.setText(translate("menu", "Undo"))
|
|
self.mainWindow.ui.actionUndo.setEnabled(False)
|
|
|
|
|
|
if redoList:
|
|
self.mainWindow.ui.actionRedo.setText(translate("menu", "Redo: {}").format(redoList[-1]))
|
|
self.mainWindow.ui.actionRedo.setEnabled(True)
|
|
else:
|
|
self.mainWindow.ui.actionRedo.setText(translate("menu", "Redo"))
|
|
self.mainWindow.ui.actionRedo.setEnabled(False)
|
|
|
|
def exportLy(self):
|
|
lastExportDirectory = api.session.guiSharedDataToSave["lastExportDirectory"] if "lastExportDirectory" in api.session.guiSharedDataToSave else str(Path.home())
|
|
filename = QtWidgets.QFileDialog.getSaveFileName(self.mainWindow, translate("menu", "Export Lilypond Source File"), lastExportDirectory, translate("menu", "Lilypond Source (*.ly)"))
|
|
filename = filename[0] #(path, filter)
|
|
if filename:
|
|
if not os.path.splitext(filename)[1]: #no file extension given?
|
|
filename = filename + ".ly"
|
|
api.session.guiSharedDataToSave["lastExportDirectory"] = os.path.dirname(filename)
|
|
api.exportLilypond(filename)
|
|
|
|
class ToolBarCCType(QtWidgets.QSpinBox):
|
|
def __init__(self, mainWindow):
|
|
super().__init__()
|
|
self.mainWindow = mainWindow
|
|
self.setRange(0,127)
|
|
self.setPrefix("CC ")
|
|
|
|
self.valueChanged.connect(self.changed)
|
|
|
|
#self.addItems([str(i).zfill(3) for i in range(128) ])
|
|
|
|
def changed(self):
|
|
constantsAndConfigs.ccViewValue = self.value()
|
|
self.mainWindow.scoreView.updateMode() #gets the new value itself from constantsAndConfigs
|
|
|
|
class ToolBarSnapToGrid(QtWidgets.QCheckBox):
|
|
def __init__(self, mainWindow):
|
|
super().__init__(translate("menu", "Snap to Grid"))
|
|
self.setStatus()
|
|
self.stateChanged.connect(self.changed)
|
|
|
|
constantsAndConfigs.snapToGridCallbacks.append(self.updateFromCallback)
|
|
|
|
def setStatus(self):
|
|
if constantsAndConfigs.snapToGrid:
|
|
self.setCheckState(QtCore.Qt.Checked)
|
|
else:
|
|
self.setCheckState(QtCore.Qt.Unchecked)
|
|
|
|
def isChecked(self):
|
|
if self.checkState() == QtCore.Qt.Checked:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def updateFromCallback(self):
|
|
self.blockSignals(True)
|
|
self.setStatus()
|
|
self.blockSignals(False)
|
|
|
|
def changed(self):
|
|
constantsAndConfigs.snapToGrid = self.isChecked() #this sends a call back to all Snap to Grid in the whole program
|
|
|
|
|
|
class ToolBarMetronome(QtWidgets.QCheckBox):
|
|
"""Contrary to SnapToGrid this reflects a backend state, not a pure GUI one.
|
|
There is also the menu actionMetronome_Enabled , itself a normal checkbox. It's callback and
|
|
state are handled in the menuDatabase classes init."""
|
|
def __init__(self, mainWindow):
|
|
super().__init__(translate("menu", "Metronome"))
|
|
self.setStatus()
|
|
self.stateChanged.connect(self.changed)
|
|
api.callbacks.metronomeChanged.append(self.updateFromCallback)
|
|
|
|
def setStatus(self):
|
|
if api.isMetronomeEnabled():
|
|
self.setCheckState(QtCore.Qt.Checked)
|
|
else:
|
|
self.setCheckState(QtCore.Qt.Unchecked)
|
|
|
|
def isChecked(self):
|
|
if self.checkState() == QtCore.Qt.Checked:
|
|
assert api.isMetronomeEnabled(), api.isMetronomeEnabled()
|
|
return True
|
|
else:
|
|
assert not api.isMetronomeEnabled(), api.isMetronomeEnabled()
|
|
return False
|
|
|
|
def updateFromCallback(self, exportDict):
|
|
"""Gets the metronome dict"""
|
|
self.blockSignals(True)
|
|
self.setStatus()
|
|
assert self.isChecked() == exportDict["enabled"], (self.isChecked(), exportDict["enabled"])
|
|
self.setText(translate("menu", "Metronome:") + " " + exportDict["label"])
|
|
self.blockSignals(False)
|
|
|
|
def changed(self, value):
|
|
api.enableMetronome(value) #triggers callback
|
|
|
|
|
|
class ToolBarPlaybackSpeed(QtWidgets.QDoubleSpinBox):
|
|
def __init__(self, mainWindow):
|
|
super().__init__()
|
|
self.setRange(0.1,3.0)
|
|
self.setDecimals(1)
|
|
self.setValue(api.currentTempoScalingFactor())
|
|
self.setSingleStep(0.1)
|
|
self.setPrefix(translate("menu", "Tempo ×")) #yes, this is the unicode multiplication sign × and not an x
|
|
api.callbacks.tempoScalingChanged.append(self.updateFromCallback)
|
|
self.valueChanged.connect(self.changed)
|
|
self.setLineEdit(ToolBarPlaybackSpeed.CustomLineEdit())
|
|
|
|
class CustomLineEdit(QtWidgets.QLineEdit):
|
|
def mousePressEvent(self, event):
|
|
if event.button() == 4:
|
|
self.parentWidget().setValue(1.0)
|
|
self.parentWidget().changed()
|
|
else:
|
|
event.ignore()
|
|
super().mousePressEvent(event)
|
|
|
|
def mousePressEvent(self, event):
|
|
if event.button() == 4:
|
|
self.setValue(1.0)
|
|
self.changed()
|
|
else:
|
|
event.ignore()
|
|
super().mousePressEvent(event)
|
|
|
|
def updateFromCallback(self, newValue):
|
|
self.blockSignals(True)
|
|
self.setValue(newValue)
|
|
self.blockSignals(False)
|
|
|
|
def changed(self):
|
|
api.changeTempoScaling(self.value())
|
|
|