Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 54 additions & 19 deletions music21/configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@

def writeToUser(msg, wrapLines=True, linesPerPage=20):
'''
Display a message to the user, handling multiple lines as necessary and wrapping text.
Display a message to the user, handling multiple lines as necessary and wrapping text
'''
# wrap everything to 60 lines
if common.isListLike(msg):
Expand Down Expand Up @@ -122,7 +122,7 @@ def getSitePackages():

def getUserData():
'''
Return a dictionary with user data.
Return a dictionary with user data
'''
post = {}
try:
Expand Down Expand Up @@ -236,7 +236,7 @@ class Dialog:
Model a dialog as a question and response. Have different subclasses for
different types of questions. Store all in a Conversation, or multiple dialog passes.

A `default`, if provided, is returned if the user provides no input and just enters return.
A `default`, if provided, is returned if the users provides no input and just enters return.

The `tryAgain` option determines if, if a user provides incomplete or no response,
and there is no default (for no response), whether the user is given another chance
Expand Down Expand Up @@ -274,7 +274,7 @@ def __init__(self, default=None, tryAgain=True, promptHeader=None):

def _writeToUser(self, msg):
'''
Write output to user. Call module-level function.
Write output to user. Call module-level function
'''
writeToUser(msg)

Expand Down Expand Up @@ -333,7 +333,7 @@ def appendPromptHeader(self, msg):

def _askTryAgain(self, default=True, force=None):
'''
What to do if input is incomplete.
What to do if input is incomplete

>>> prompt = configure.YesOrNo(default=True)
>>> prompt._askTryAgain(force='yes')
Expand Down Expand Up @@ -386,7 +386,7 @@ def _rawQueryPrepareHeader(self, msg=''):

def _rawQueryPrepareFooter(self, msg=''):
'''
Prepare the end of the query message.
Prepare the end of the query message
'''
if self._default is not None:
msg = msg.strip()
Expand Down Expand Up @@ -706,7 +706,7 @@ def _evaluateUserInput(self, raw):
# ------------------------------------------------------------------------------
class AskOpenInBrowser(YesOrNo):
'''
Ask the user if they want to open a URL in a browser.
Ask the user if the want to open a URL in a browser.

>>> d = configure.AskOpenInBrowser('https://www.music21.org/')
'''
Expand Down Expand Up @@ -833,7 +833,7 @@ def _getValidResults(self, force=None):

def _formatResultForUser(self, result):
'''
Reduce each complete file path to stub, or otherwise compact display.
Reduce each complete file path to stub, or otherwise compact display
'''
return result

Expand Down Expand Up @@ -997,7 +997,7 @@ def _rawIntroduction(self):

def _getValidResults(self, force=None):
'''
Just return number options.
Just return number options
'''
if force is not None:
return force
Expand Down Expand Up @@ -1034,7 +1034,7 @@ def _evaluateUserInput(self, raw):

def _performAction(self, simulate=False):
'''
Override base.
override base.
'''
result = self.getResult()
if result in [1, 2, 3]:
Expand Down Expand Up @@ -1069,7 +1069,7 @@ def _getAppOSIndependent(self, comparisonFunction, path0: str, post: list[str],
'''
Uses comparisonFunction to see if a file in path0 matches
the RE embedded in comparisonFunction and if so manipulate the list
in post.
in post

comparisonFunction = function (lambda function on path returning True/False)
path0 = os-specific string
Expand All @@ -1084,7 +1084,7 @@ def _getAppOSIndependent(self, comparisonFunction, path0: str, post: list[str],
def _getDarwinApp(self, comparisonFunction) -> list[str]:
'''
Provide a comparison function that returns True or False based on the file name.
This looks at everything in Applications, as well as every directory in Applications.
This looks at everything in Applications, as well as every directory in Applications
'''
post: list[str] = []
for path0 in ('/Applications', common.cleanpath('~/Applications', returnPathlib=False)):
Expand Down Expand Up @@ -1165,7 +1165,7 @@ def _rawIntroduction(self):

def _getMusicXMLReaderDarwin(self):
'''
Get all possible MusicXML Reader paths on Darwin (i.e., macOS).
Get all possible MusicXML Reader paths on Darwin (i.e., macOS)
'''
def comparisonFinale(x):
return reFinaleApp.search(x) is not None
Expand Down Expand Up @@ -1199,7 +1199,7 @@ def comparisonDorico(x):

def _getMusicXMLReaderWin(self):
'''
Get all possible MusicXML Reader paths on Windows.
Get all possible MusicXML Reader paths on Windows
'''
def comparisonFinale(x):
return reFinaleExe.search(x) is not None
Expand Down Expand Up @@ -1229,7 +1229,7 @@ def comparisonDorico(x):

def _getMusicXMLReaderNix(self):
'''
Get all possible MusicXML Reader paths on Unix.
Get all possible MusicXML Reader paths on Unix
'''
return []

Expand Down Expand Up @@ -1291,20 +1291,55 @@ def _askFillEmptyList(self, default=None, force=None):

return post

def _getMuseScorePNGPathFromApp(self, musicxmlPath: str) -> str:
'''
Given the path to a MuseScore application bundle (macOS) or executable,
return the path suitable for headless PNG generation
(`musescoreDirectPNGPath`).
'''
platform = common.getPlatform()
if platform == 'darwin' and musicxmlPath.endswith('.app'):
# Typical structure: /Applications/MuseScore 4.app/Contents/MacOS/mscore
executable = os.path.join(musicxmlPath, 'Contents', 'MacOS', 'mscore')
if os.path.isfile(executable):
return executable
else:
# Fallback: some older MuseScore versions use 'MuseScore'
fallback = os.path.join(musicxmlPath, 'Contents', 'MacOS', 'MuseScore')
if os.path.isfile(fallback):
return fallback
# If neither is found, return the original path (best effort)
environLocal.printDebug(
f'Could not find MuseScore executable inside {musicxmlPath}; '
f'using app bundle path as fallback.')
return musicxmlPath
# On Windows the found path is already the .exe, on Linux/nix we assume it’s correct.
return musicxmlPath

def _performAction(self, simulate=False):
'''
The action here is to open the stored URL in a browser, if the user agrees.
The action here is to set the musicxmlPath and, when the reader is MuseScore,
also set the musescoreDirectPNGPath.
'''
result = self.getResult()
if result is not None and not isinstance(result, DialogError):
# noinspection PyTypeChecker
reload(environment)
# us = environment.UserSettings()
# us['musicxmlPath'] = result # automatically writes
# Set the MusicXML reader path
environment.set('musicxmlPath', result)
musicXmlNew = environment.get('musicxmlPath')
self._writeToUser([f'MusicXML Reader set to: {musicXmlNew}', ' '])

# If the chosen reader is MuseScore, also set the PNG generation path
# to avoid stale/broken values.
if 'MuseScore' in result:
pngPath = self._getMuseScorePNGPathFromApp(result)
environment.set('musescoreDirectPNGPath', pngPath)
pngPathCheck = environment.get('musescoreDirectPNGPath')
self._writeToUser([
f'MuseScore PNG generator set to: {pngPathCheck}',
' '
])


# ------------------------------------------------------------------------------
class ConfigurationAssistant:
Expand Down
Loading