Browse Source

Add settings dialog with option to add custom executable paths and a white- and blacklist for the program launcher. This should make custom scenarios much easier without compromising the 'everything must be in the PATH' rule

master
Nils 4 years ago
parent
commit
fd11e1b662
  1. 17
      engine/api.py
  2. 85
      engine/findprograms.py
  3. 45
      qtgui/addclientprompt.py
  4. 9
      qtgui/designer/mainwindow.py
  5. 6
      qtgui/designer/mainwindow.ui
  6. 2
      qtgui/designer/newsession.py
  7. 2
      qtgui/designer/newsession.ui
  8. 82
      qtgui/designer/settings.py
  9. 142
      qtgui/designer/settings.ui
  10. 24
      qtgui/mainwindow.py
  11. BIN
      qtgui/resources/translations/de.qm
  12. 32
      qtgui/resources/translations/de.ts
  13. 116
      qtgui/settings.py

17
engine/api.py

@ -152,7 +152,7 @@ def startEngine():
dataClientNamesHook=callbacks._dataClientNamesChanged, dataClientNamesHook=callbacks._dataClientNamesChanged,
dataClientDescriptionHook=callbacks._dataClientDescriptionChanged, dataClientDescriptionHook=callbacks._dataClientDescriptionChanged,
parameterNsmOSCUrl=PATHS["url"], parameterNsmOSCUrl=PATHS["url"],
sessionRoot=PATHS["sessionRoot"], sessionRoot=PATHS["sessionRoot"],
) )
#Watch session tree for changes. #Watch session tree for changes.
@ -198,7 +198,17 @@ def buildSystemPrograms():
"""Build a list of dicts with the .desktop files (or similar) of all NSM compatible programs """Build a list of dicts with the .desktop files (or similar) of all NSM compatible programs
present on the system""" present on the system"""
programDatabase.build() programDatabase.build()
def systemProgramsSetWhitelist(executableNames:tuple):
"""will replace the current list"""
programDatabase.userWhitelist = tuple(executableNames)
#Needs rebuild through the GUI. We have no callback for this.
def systemProgramsSetBlacklist(executableNames:tuple):
"""will replace the current list"""
programDatabase.userBlacklist = tuple(executableNames)
#Needs rebuild through the GUI. We have no callback for this.
def getSystemPrograms()->list: def getSystemPrograms()->list:
"""Returns the cached database from buildProgramDatabase. No automatic update. Empty on program """Returns the cached database from buildProgramDatabase. No automatic update. Empty on program
start""" start"""
@ -214,7 +224,8 @@ def getNsmExecutables()->set:
def getUnfilteredExecutables()->list: def getUnfilteredExecutables()->list:
"""Return a list of unique names without paths or directories of all exectuables in users $PATH. """Return a list of unique names without paths or directories of all exectuables in users $PATH.
This is intended for a program starter prompt. GUI needs to supply tab completition or search This is intended for a program starter prompt. GUI needs to supply tab completition or search
itself""" itself"""
programDatabase.unfilteredExecutables = programDatabase.buildCache_unfilteredExecutables() #quick call
return programDatabase.unfilteredExecutables return programDatabase.unfilteredExecutables

85
engine/findprograms.py

@ -48,7 +48,7 @@ class SupportedProgramsDatabase(object):
'name': 'Patroneo', 'name': 'Patroneo',
'startupnotify': 'false', 'startupnotify': 'false',
'terminal': 'false', 'terminal': 'false',
'type': 'Application', 'type': 'Application',
'x-nsm-capable': 'true'} 'x-nsm-capable': 'true'}
In case there is a file in PATH or database but has no .desktop we create our own entry with In case there is a file in PATH or database but has no .desktop we create our own entry with
@ -57,16 +57,19 @@ class SupportedProgramsDatabase(object):
def __init__(self): def __init__(self):
self.blacklist = ("nsmd", "non-daw", "carla") self.blacklist = ("nsmd", "non-daw", "carla")
self.morePrograms = ("thisdoesnotexisttest", "carla-rack", "carla-patchbay", "carla-jack-multi", "ardour5", "ardour6", "nsm-data", "nsm-jack") #only programs not found by buildCache_grepExecutablePaths because they are just shellscripts and do not contain /nsm/server/announce. self.morePrograms = ("thisdoesnotexisttest", "carla-rack", "carla-patchbay", "carla-jack-multi", "ardour5", "ardour6", "nsm-data", "nsm-jack") #only programs not found by buildCache_grepExecutablePaths because they are just shellscripts and do not contain /nsm/server/announce.
self.userWhitelist = () #added dynamically to morePrograms
self.userBlacklist = () #added dynamically to blacklist
self.knownDesktopFiles = { #shortcuts to the correct desktop files. Reverse lookup binary->desktop creates false entries, for example ZynAddSubFx and Carla. self.knownDesktopFiles = { #shortcuts to the correct desktop files. Reverse lookup binary->desktop creates false entries, for example ZynAddSubFx and Carla.
"zynaddsubfx": "zynaddsubfx-jack.desktop", #value will later get replaced with the .desktop entry "zynaddsubfx": "zynaddsubfx-jack.desktop", #value will later get replaced with the .desktop entry
"carla-jack-multi" : "carla.desktop", "carla-jack-multi" : "carla.desktop",
} }
self.programs = [] #list of dicts. guaranteed keys: argodejoExec, name, argodejoFullPath. And probably others, like description and version. self.programs = [] #list of dicts. guaranteed keys: argodejoExec, name, argodejoFullPath. And probably others, like description and version.
self.nsmExecutables = set() #set of executables for fast membership, if a GUI wants to know if they are available. Needs to be build "manually" with self.programs. no auto-property for a list. at least we don't want to do the work. self.nsmExecutables = set() #set of executables for fast membership, if a GUI wants to know if they are available. Needs to be build "manually" with self.programs. no auto-property for a list. at least we don't want to do the work.
#.build needs to be called from the api/GUI. #.build needs to be called from the api/GUI.
self.unfilteredExecutables = self.buildCache_unfilteredExecutables() #This doesn't take too long. we can start that every time. It will get updated in build as well. #self.unfilteredExecutables = self.buildCache_unfilteredExecutables() #This doesn't take too long. we can start that every time. It will get updated in build as well.
#self.build() #fills self.programs and self.unfilteredExecutables = None #in build()
#self.build() #fills self.programs and
def buildCache_grepExecutablePaths(self): def buildCache_grepExecutablePaths(self):
"""return a list of executable names in the path """return a list of executable names in the path
@ -74,24 +77,28 @@ class SupportedProgramsDatabase(object):
-s silent. No errors, eventhough subprocess uses stdout only -s silent. No errors, eventhough subprocess uses stdout only
-R recursive with symlinks. We don't want to look in subdirs because that is not allowed by -R recursive with symlinks. We don't want to look in subdirs because that is not allowed by
PATH and nsm, but we want to follow symlinks PATH and nsm, but we want to follow symlinks
If you have a custom user path that does not mean that all its executables will
automatically show up here. They still need to contain /nsm/server/announce
Your binaries will be in unfilteredExecutables though
""" """
result = [] result = []
executablePaths = [pathlib.Path(p) for p in os.environ["PATH"].split(":")] executablePaths = [pathlib.Path(p) for p in os.environ["PATH"].split(os.pathsep)]
for path in executablePaths: for path in executablePaths:
command = f"grep -iRsnl {path} -e /nsm/server/announce" command = f"grep -iRsnl {path} -e /nsm/server/announce"
#Py>=3.7 completedProcess = subprocess.run(command, capture_output=True, text=True, shell=True) #Py>=3.7 completedProcess = subprocess.run(command, capture_output=True, text=True, shell=True)
completedProcess = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, shell=True) #universal_newlines is an alias for text, which was deprecated in 3.7 because text is more understandable. capture_output replaces the two PIPEs in 3.7 completedProcess = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, shell=True) #universal_newlines is an alias for text, which was deprecated in 3.7 because text is more understandable. capture_output replaces the two PIPEs in 3.7
for fullPath in completedProcess.stdout.split(): for fullPath in completedProcess.stdout.split():
exe = pathlib.Path(fullPath).relative_to(path) exe = pathlib.Path(fullPath).relative_to(path)
if not str(exe) in self.blacklist: if not str(exe) in self.blacklist + self.userBlacklist:
result.append((str(exe), str(fullPath))) result.append((str(exe), str(fullPath)))
for prg in self.morePrograms: for prg in self.morePrograms + self.userWhitelist:
for path in executablePaths: for path in executablePaths:
if pathlib.Path(path, prg).is_file(): if pathlib.Path(path, prg).is_file():
result.append((str(prg), str(pathlib.Path(path, prg)))) result.append((str(prg), str(pathlib.Path(path, prg))))
break #inner loop break #inner loop
return result return result
def buildCache_DesktopEntries(self): def buildCache_DesktopEntries(self):
@ -109,7 +116,7 @@ class SupportedProgramsDatabase(object):
if f.is_file() and f.suffix == ".desktop": if f.is_file() and f.suffix == ".desktop":
config.clear() config.clear()
config.read(f) config.read(f)
entryDict = dict(config._sections["Desktop Entry"]) entryDict = dict(config._sections["Desktop Entry"])
if f.name == "zynaddsubfx-jack.desktop": if f.name == "zynaddsubfx-jack.desktop":
self.knownDesktopFiles["zynaddsubfx"] = entryDict self.knownDesktopFiles["zynaddsubfx"] = entryDict
elif f.name == "carla.desktop": elif f.name == "carla.desktop":
@ -124,7 +131,7 @@ class SupportedProgramsDatabase(object):
self.nsmExecutables = set(d["argodejoExec"] for d in self.programs) self.nsmExecutables = set(d["argodejoExec"] for d in self.programs)
def build(self): def build(self):
"""Can be called at any time by the user to update after installing new programs""" """Can be called at any time by the user to update after installing new programs"""
logger.info("Building launcher database. This might take a minute") logger.info("Building launcher database. This might take a minute")
self.programs = self._build() self.programs = self._build()
self.unfilteredExecutables = self.buildCache_unfilteredExecutables() self.unfilteredExecutables = self.buildCache_unfilteredExecutables()
@ -137,40 +144,40 @@ class SupportedProgramsDatabase(object):
Convert one exe (not full path!) to one dict entry. """ Convert one exe (not full path!) to one dict entry. """
if exe in self.knownDesktopFiles: #Shortcut through internal database if exe in self.knownDesktopFiles: #Shortcut through internal database
entry = self.knownDesktopFiles[exe] entry = self.knownDesktopFiles[exe]
else: #Reverse Search desktop files. else: #Reverse Search desktop files.
for entry in self.desktopEntries: for entry in self.desktopEntries:
if "exec" in entry and exe.lower() in entry["exec"].lower(): if "exec" in entry and exe.lower() in entry["exec"].lower():
return entry return entry
#else: #Foor loop ended. Did not find any matching desktop file #else: #Foor loop ended. Did not find any matching desktop file
return None return None
def _build(self): def _build(self):
self.executables = self.buildCache_grepExecutablePaths() self.executables = self.buildCache_grepExecutablePaths()
self.desktopEntries = self.buildCache_DesktopEntries() self.desktopEntries = self.buildCache_DesktopEntries()
leftovers = set(self.executables) leftovers = set(self.executables)
matches = [] #list of dicts matches = [] #list of dicts
for exe, fullPath in self.executables: for exe, fullPath in self.executables:
entry = self._exeToDesktopEntry(exe) entry = self._exeToDesktopEntry(exe)
if entry: #Found match! if entry: #Found match!
entry["argodejoFullPath"] = fullPath entry["argodejoFullPath"] = fullPath
#We don't want .desktop syntax like "qmidiarp %F" #We don't want .desktop syntax like "qmidiarp %F"
entry["argodejoExec"] = exe entry["argodejoExec"] = exe
matches.append(entry) matches.append(entry)
try: try:
leftovers.remove((exe,fullPath)) leftovers.remove((exe,fullPath))
except KeyError: except KeyError:
pass #Double entries like zyn-jack zyn-alsa etc. pass #Double entries like zyn-jack zyn-alsa etc.
""" """
if exe in self.knownDesktopFiles: #Shortcut through internal database if exe in self.knownDesktopFiles: #Shortcut through internal database
entry = self.knownDesktopFiles[exe] entry = self.knownDesktopFiles[exe]
else: #Reverse Search desktop files. else: #Reverse Search desktop files.
for entry in self.desktopEntries: for entry in self.desktopEntries:
if "exec" in entry and exe.lower() in entry["exec"].lower(): if "exec" in entry and exe.lower() in entry["exec"].lower():
break #found entry. break inner loop to keep it break #found entry. break inner loop to keep it
else: #Foor loop ended. Did not find any matching desktop file else: #Foor loop ended. Did not find any matching desktop file
#If we omit continue it will just write exe and fullPath in any desktop file. #If we omit continue it will just write exe and fullPath in any desktop file.
@ -181,28 +188,28 @@ class SupportedProgramsDatabase(object):
#We don't want .desktop syntax like "qmidiarp %F" #We don't want .desktop syntax like "qmidiarp %F"
entry["argodejoExec"] = exe entry["argodejoExec"] = exe
matches.append(entry) matches.append(entry)
try: try:
leftovers.remove((exe,fullPath)) leftovers.remove((exe,fullPath))
except KeyError: except KeyError:
pass #Double entries like zyn-jack zyn-alsa etc. pass #Double entries like zyn-jack zyn-alsa etc.
""" """
for exe,fullPath in leftovers: for exe,fullPath in leftovers:
pseudoEntry = {"name": exe.title(), "argodejoExec":exe, "argodejoFullPath":fullPath} pseudoEntry = {"name": exe.title(), "argodejoExec":exe, "argodejoFullPath":fullPath}
matches.append(pseudoEntry) matches.append(pseudoEntry)
return matches return matches
def buildCache_unfilteredExecutables(self): def buildCache_unfilteredExecutables(self):
def isexe(path): def isexe(path):
"""executable by owner""" """executable by owner"""
return path.is_file() and stat.S_IXUSR & os.stat(path)[stat.ST_MODE] == 64 return path.is_file() and stat.S_IXUSR & os.stat(path)[stat.ST_MODE] == 64
result = [] result = []
executablePaths = [pathlib.Path(p) for p in os.environ["PATH"].split(":")] executablePaths = [pathlib.Path(p) for p in os.environ["PATH"].split(os.pathsep)]
for path in executablePaths: for path in executablePaths:
result += [str(pathlib.Path(f).relative_to(path)) for f in path.glob("*") if isexe(f)] result += [str(pathlib.Path(f).relative_to(path)) for f in path.glob("*") if isexe(f)]
return sorted(list(set(result))) return sorted(list(set(result)))
def _buildWhitelist(self): def _buildWhitelist(self):
@ -210,19 +217,19 @@ class SupportedProgramsDatabase(object):
It will be populated from a template-list of well-working clients and then all binaries not It will be populated from a template-list of well-working clients and then all binaries not
in the path are filtered out. This can be presented to the user without worries.""" in the path are filtered out. This can be presented to the user without worries."""
#Assumes to be called only from self.build #Assumes to be called only from self.build
startexecutables = set(("doesnotexist", "laborejo2", "patroneo", "vico", "fluajho", "carla-rack", "carla-patchbay", "carla-jack-multi", "ardour6", "drumkv1_jack", "synthv1_jack", "padthv1_jack", "samplv1_jack", "zynaddsubfx", "ADLplug", "OPNplug", "non-mixer", "non-sequencer", "non-timeline")) startexecutables = set(("doesnotexist", "laborejo", "patroneo", "vico", "fluajho", "carla-rack", "carla-patchbay", "carla-jack-multi", "ardour6", "drumkv1_jack", "synthv1_jack", "padthv1_jack", "samplv1_jack", "zynaddsubfx", "ADLplug", "OPNplug", "non-mixer", "non-sequencer", "non-timeline") + self.userWhitelist)
for prog in self.programs: for prog in self.programs:
prog["whitelist"] = prog["argodejoExec"] in startexecutables prog["whitelist"] = prog["argodejoExec"] in startexecutables
""" """
matches = [] matches = []
for exe in startexecutables: for exe in startexecutables:
entry = self._exeToDesktopEntry(exe) entry = self._exeToDesktopEntry(exe)
if entry: #Found match! if entry: #Found match!
#We don't want .desktop syntax like "qmidiarp %F" #We don't want .desktop syntax like "qmidiarp %F"
entry["argodejoExec"] = exe entry["argodejoExec"] = exe
matches.append(entry) matches.append(entry)
return matches return matches
""" """

45
qtgui/addclientprompt.py

@ -37,7 +37,10 @@ class PromptWidget(QtWidgets.QDialog):
def __init__(self, parent): def __init__(self, parent):
super().__init__(parent) super().__init__(parent)
layout = QtWidgets.QFormLayout() layout = QtWidgets.QFormLayout()
#layout = QtWidgets.QVBoxLayout() #layout = QtWidgets.QVBoxLayout()
updateWordlist() #this is a fast index update, we can call that every time to be sure. Otherwise newly added executable-PATHs will not be reflected in the dialog without a program-database update, which takes too long to be convenient.
self.setLayout(layout) self.setLayout(layout)
self.setWindowFlag(QtCore.Qt.Popup, True) self.setWindowFlag(QtCore.Qt.Popup, True)
@ -45,26 +48,26 @@ class PromptWidget(QtWidgets.QDialog):
self.comboBox.setEditable(True) self.comboBox.setEditable(True)
if PromptWidget.wordlist: if PromptWidget.wordlist:
completer = QtWidgets.QCompleter(PromptWidget.wordlist) completer = QtWidgets.QCompleter(PromptWidget.wordlist)
completer.setModelSorting(QtWidgets.QCompleter.CaseInsensitivelySortedModel) completer.setModelSorting(QtWidgets.QCompleter.CaseInsensitivelySortedModel)
completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive) completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
completer.setCompletionMode(QtWidgets.QCompleter.PopupCompletion) completer.setCompletionMode(QtWidgets.QCompleter.PopupCompletion)
completer.activated.connect(self.process) #To avoid double-press enter to select one we hook into the activated signal and trigger process completer.activated.connect(self.process) #To avoid double-press enter to select one we hook into the activated signal and trigger process
self.comboBox.setCompleter(completer) self.comboBox.setCompleter(completer)
self.comboBox.setMinimumContentsLength(PromptWidget.minlen) self.comboBox.setMinimumContentsLength(PromptWidget.minlen)
labelString = QtCore.QCoreApplication.translate("PromptWidget", "Type in the name of an executable file on your system.") labelString = QtCore.QCoreApplication.translate("PromptWidget", "Type in the name of an executable file on your system.")
else: else:
labelString = QtCore.QCoreApplication.translate("PromptWidget", "No program database found. Please update through Control menu.") labelString = QtCore.QCoreApplication.translate("PromptWidget", "No program database found. Please update through Control menu.")
label = QtWidgets.QLabel(labelString) label = QtWidgets.QLabel(labelString)
layout.addWidget(label) layout.addWidget(label)
self.comboBox.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToMinimumContentsLength) self.comboBox.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToMinimumContentsLength)
layout.addWidget(self.comboBox) layout.addWidget(self.comboBox)
errorString = QtCore.QCoreApplication.translate("PromptWidget", "Command not accepted!<br>Parameters, --switches and relative paths are not allowed.<br>Use nsm-proxy or write a starter-script instead.") errorString = QtCore.QCoreApplication.translate("PromptWidget", "Command not accepted!<br>Parameters, --switches and relative paths are not allowed.<br>Use nsm-proxy or write a starter-script instead.")
errorString = "<i>" + errorString + "</i>" errorString = "<i>" + errorString + "</i>"
self.errorLabel = QtWidgets.QLabel(errorString) self.errorLabel = QtWidgets.QLabel(errorString)
layout.addWidget(self.errorLabel) layout.addWidget(self.errorLabel)
self.errorLabel.hide() #shown in process self.errorLabel.hide() #shown in process
@ -72,10 +75,8 @@ class PromptWidget(QtWidgets.QDialog):
buttonBox.accepted.connect(self.process) buttonBox.accepted.connect(self.process)
buttonBox.rejected.connect(self.reject) buttonBox.rejected.connect(self.reject)
layout.addWidget(buttonBox) layout.addWidget(buttonBox)
self.exec() #blocks until the dialog gets closed
self.exec() #blocks until the dialog gets closed
def abortHandler(self): def abortHandler(self):
pass pass
@ -85,8 +86,12 @@ class PromptWidget(QtWidgets.QDialog):
Make sure your objects exists and your syntax is correct""" Make sure your objects exists and your syntax is correct"""
assert PromptWidget.wordlist assert PromptWidget.wordlist
assert PromptWidget.minlen assert PromptWidget.minlen
curText = self.comboBox.currentText() curText = self.comboBox.currentText()
if not curText or curText == " ": #TODO: qt weirdness. This is a case when focus is lost from a valid entry. The field is filled from a chosen value, from the list. But it says " ".
#Do not show the errorLabel. This is a qt bug or so.
return
if curText in PromptWidget.wordlist: if curText in PromptWidget.wordlist:
api.clientAdd(curText) api.clientAdd(curText)
logger.info(f"Prompt accepted {curText} and will send it to clientAdd") logger.info(f"Prompt accepted {curText} and will send it to clientAdd")
@ -94,8 +99,8 @@ class PromptWidget(QtWidgets.QDialog):
else: else:
logger.info(f"Prompt did not accept {curText}.Showing info to the user.") logger.info(f"Prompt did not accept {curText}.Showing info to the user.")
self.errorLabel.show() self.errorLabel.show()
def updateWordlist(): def updateWordlist():
"""in case programs are installed while the session is running the user can """in case programs are installed while the session is running the user can
manually call a database update""" manually call a database update"""
@ -104,6 +109,6 @@ def updateWordlist():
PromptWidget.minlen = len(max(PromptWidget.wordlist, key=len)) PromptWidget.minlen = len(max(PromptWidget.wordlist, key=len))
else: else:
logger.error("Executable list came back empty! Most likely an error in application database build. Not trivial!") logger.error("Executable list came back empty! Most likely an error in application database build. Not trivial!")
def askForExecutable(parent): def askForExecutable(parent):
PromptWidget(parent) PromptWidget(parent)

9
qtgui/designer/mainwindow.py

@ -2,9 +2,10 @@
# Form implementation generated from reading ui file 'mainwindow.ui' # Form implementation generated from reading ui file 'mainwindow.ui'
# #
# Created by: PyQt5 UI code generator 5.14.2 # Created by: PyQt5 UI code generator 5.15.0
# #
# WARNING! All changes made in this file will be lost! # WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
@ -310,8 +311,11 @@ class Ui_MainWindow(object):
self.actionRebuild_Program_Database.setObjectName("actionRebuild_Program_Database") self.actionRebuild_Program_Database.setObjectName("actionRebuild_Program_Database")
self.actionClientRename = QtWidgets.QAction(MainWindow) self.actionClientRename = QtWidgets.QAction(MainWindow)
self.actionClientRename.setObjectName("actionClientRename") self.actionClientRename.setObjectName("actionClientRename")
self.actionSettings = QtWidgets.QAction(MainWindow)
self.actionSettings.setObjectName("actionSettings")
self.menuControl.addAction(self.actionRebuild_Program_Database) self.menuControl.addAction(self.actionRebuild_Program_Database)
self.menuControl.addAction(self.actionHide_in_System_Tray) self.menuControl.addAction(self.actionHide_in_System_Tray)
self.menuControl.addAction(self.actionSettings)
self.menuControl.addAction(self.actionMenuQuit) self.menuControl.addAction(self.actionMenuQuit)
self.menuSession.addAction(self.actionSessionAddClient) self.menuSession.addAction(self.actionSessionAddClient)
self.menuSession.addAction(self.actionSessionSave) self.menuSession.addAction(self.actionSessionSave)
@ -405,3 +409,4 @@ class Ui_MainWindow(object):
self.actionRebuild_Program_Database.setText(_translate("MainWindow", "Rebuild Program Database")) self.actionRebuild_Program_Database.setText(_translate("MainWindow", "Rebuild Program Database"))
self.actionClientRename.setText(_translate("MainWindow", "Rename")) self.actionClientRename.setText(_translate("MainWindow", "Rename"))
self.actionClientRename.setShortcut(_translate("MainWindow", "F2")) self.actionClientRename.setShortcut(_translate("MainWindow", "F2"))
self.actionSettings.setText(_translate("MainWindow", "Settings"))

6
qtgui/designer/mainwindow.ui

@ -636,6 +636,7 @@
</property> </property>
<addaction name="actionRebuild_Program_Database"/> <addaction name="actionRebuild_Program_Database"/>
<addaction name="actionHide_in_System_Tray"/> <addaction name="actionHide_in_System_Tray"/>
<addaction name="actionSettings"/>
<addaction name="actionMenuQuit"/> <addaction name="actionMenuQuit"/>
</widget> </widget>
<widget class="QMenu" name="menuSession"> <widget class="QMenu" name="menuSession">
@ -796,6 +797,11 @@
<string>F2</string> <string>F2</string>
</property> </property>
</action> </action>
<action name="actionSettings">
<property name="text">
<string>Settings</string>
</property>
</action>
</widget> </widget>
<resources/> <resources/>
<connections/> <connections/>

2
qtgui/designer/newsession.py

@ -47,6 +47,6 @@ class Ui_NewSession(object):
NewSession.setWindowTitle(_translate("NewSession", "Dialog")) NewSession.setWindowTitle(_translate("NewSession", "Dialog"))
self.nameGroupBox.setTitle(_translate("NewSession", "New Session Name")) self.nameGroupBox.setTitle(_translate("NewSession", "New Session Name"))
self.checkBoxJack.setText(_translate("NewSession", "Save JACK Connections\n" self.checkBoxJack.setText(_translate("NewSession", "Save JACK Connections\n"
"(adds clients \'nsm-jack\')")) "(adds clients \'jackpatch\')"))
self.checkBoxData.setText(_translate("NewSession", "Client Renaming and Session Notes\n" self.checkBoxData.setText(_translate("NewSession", "Client Renaming and Session Notes\n"
"(adds client \'nsm-data\')")) "(adds client \'nsm-data\')"))

2
qtgui/designer/newsession.ui

@ -42,7 +42,7 @@
<widget class="QCheckBox" name="checkBoxJack"> <widget class="QCheckBox" name="checkBoxJack">
<property name="text"> <property name="text">
<string>Save JACK Connections <string>Save JACK Connections
(adds clients 'nsm-jack')</string> (adds clients 'jackpatch')</string>
</property> </property>
<property name="checked"> <property name="checked">
<bool>true</bool> <bool>true</bool>

82
qtgui/designer/settings.py

@ -0,0 +1,82 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'settings.ui'
#
# Created by: PyQt5 UI code generator 5.15.0
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.resize(626, 387)
self.verticalLayout = QtWidgets.QVBoxLayout(Dialog)
self.verticalLayout.setObjectName("verticalLayout")
self.SettingsTab = QtWidgets.QTabWidget(Dialog)
self.SettingsTab.setTabPosition(QtWidgets.QTabWidget.North)
self.SettingsTab.setTabShape(QtWidgets.QTabWidget.Rounded)
self.SettingsTab.setObjectName("SettingsTab")
self.tab_2 = QtWidgets.QWidget()
self.tab_2.setObjectName("tab_2")
self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.tab_2)
self.verticalLayout_3.setObjectName("verticalLayout_3")
self.label_help_launcher_whitelist = QtWidgets.QLabel(self.tab_2)
self.label_help_launcher_whitelist.setWordWrap(True)
self.label_help_launcher_whitelist.setObjectName("label_help_launcher_whitelist")
self.verticalLayout_3.addWidget(self.label_help_launcher_whitelist)
self.launcherWhitelistPlainTextEdit = QtWidgets.QPlainTextEdit(self.tab_2)
self.launcherWhitelistPlainTextEdit.setObjectName("launcherWhitelistPlainTextEdit")
self.verticalLayout_3.addWidget(self.launcherWhitelistPlainTextEdit)
self.label_help_launcher_blacklist = QtWidgets.QLabel(self.tab_2)
self.label_help_launcher_blacklist.setWordWrap(True)
self.label_help_launcher_blacklist.setObjectName("label_help_launcher_blacklist")
self.verticalLayout_3.addWidget(self.label_help_launcher_blacklist)
self.launcherBlacklistPlainTextEdit = QtWidgets.QPlainTextEdit(self.tab_2)
self.launcherBlacklistPlainTextEdit.setObjectName("launcherBlacklistPlainTextEdit")
self.verticalLayout_3.addWidget(self.launcherBlacklistPlainTextEdit)
self.SettingsTab.addTab(self.tab_2, "")
self.tab = QtWidgets.QWidget()
self.tab.setObjectName("tab")
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.tab)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.label_help_programstart = QtWidgets.QLabel(self.tab)
self.label_help_programstart.setWordWrap(True)
self.label_help_programstart.setObjectName("label_help_programstart")
self.verticalLayout_2.addWidget(self.label_help_programstart)
self.label_help_path_rules = QtWidgets.QLabel(self.tab)
self.label_help_path_rules.setWordWrap(True)
self.label_help_path_rules.setObjectName("label_help_path_rules")
self.verticalLayout_2.addWidget(self.label_help_path_rules)
self.programPathsPlainTextEdit = QtWidgets.QPlainTextEdit(self.tab)
self.programPathsPlainTextEdit.setObjectName("programPathsPlainTextEdit")
self.verticalLayout_2.addWidget(self.programPathsPlainTextEdit)
self.SettingsTab.addTab(self.tab, "")
self.verticalLayout.addWidget(self.SettingsTab)
self.buttonBox = QtWidgets.QDialogButtonBox(Dialog)
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
self.buttonBox.setObjectName("buttonBox")
self.verticalLayout.addWidget(self.buttonBox)
self.buttonBox.raise_()
self.SettingsTab.raise_()
self.retranslateUi(Dialog)
self.SettingsTab.setCurrentIndex(0)
self.buttonBox.accepted.connect(Dialog.accept)
self.buttonBox.rejected.connect(Dialog.reject)
QtCore.QMetaObject.connectSlotsByName(Dialog)
def retranslateUi(self, Dialog):
_translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "Settings"))
self.label_help_launcher_whitelist.setText(_translate("Dialog", "Whitelist - Add executable names (not paths) to the program launcher. One executable per line."))
self.label_help_launcher_blacklist.setText(_translate("Dialog", "Blacklist - Exclude executable names (not paths) from the program launcher. One executable per line."))
self.SettingsTab.setTabText(self.SettingsTab.indexOf(self.tab_2), _translate("Dialog", "Launcher"))
self.label_help_programstart.setText(_translate("Dialog", "For advanced users only! Add executable paths to the environment, just for Argodejo and NSM. Changes need a program restart afterwards. If you want your programs in the application launcher use the launcher tab."))
self.label_help_path_rules.setText(_translate("Dialog", "Add one absolute path to a directory (e.g. /home/user/audio-bin) per line. No wildcards. Trailing slashes/ don\'t matter."))
self.SettingsTab.setTabText(self.SettingsTab.indexOf(self.tab), _translate("Dialog", "$PATH"))

142
qtgui/designer/settings.ui

@ -0,0 +1,142 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Dialog</class>
<widget class="QDialog" name="Dialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>626</width>
<height>387</height>
</rect>
</property>
<property name="windowTitle">
<string>Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTabWidget" name="SettingsTab">
<property name="tabPosition">
<enum>QTabWidget::North</enum>
</property>
<property name="tabShape">
<enum>QTabWidget::Rounded</enum>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab_2">
<attribute name="title">
<string>Launcher</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="label_help_launcher_whitelist">
<property name="text">
<string>Whitelist - Add executable names (not paths) to the program launcher. One executable per line.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPlainTextEdit" name="launcherWhitelistPlainTextEdit"/>
</item>
<item>
<widget class="QLabel" name="label_help_launcher_blacklist">
<property name="text">
<string>Blacklist - Exclude executable names (not paths) from the program launcher. One executable per line.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPlainTextEdit" name="launcherBlacklistPlainTextEdit"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab">
<attribute name="title">
<string>$PATH</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label_help_programstart">
<property name="text">
<string>For advanced users only! Add executable paths to the environment, just for Argodejo and NSM. Changes need a program restart afterwards. If you want your programs in the application launcher use the launcher tab.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_help_path_rules">
<property name="text">
<string>Add one absolute path to a directory (e.g. /home/user/audio-bin) per line. No wildcards. Trailing slashes/ don't matter.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPlainTextEdit" name="programPathsPlainTextEdit"/>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
<zorder>buttonBox</zorder>
<zorder>SettingsTab</zorder>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>Dialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>Dialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

24
qtgui/mainwindow.py

@ -48,6 +48,7 @@ from .projectname import ProjectNameWidget
from .addclientprompt import askForExecutable, updateWordlist from .addclientprompt import askForExecutable, updateWordlist
from .waitdialog import WaitDialog from .waitdialog import WaitDialog
from .resources import * from .resources import *
from .settings import SettingsDialog
api.eventLoop = EventLoop() api.eventLoop = EventLoop()
@ -115,20 +116,18 @@ class MainWindow(QtWidgets.QMainWindow):
self.fPalBlue = setPaletteAndFont(self.qtApp) self.fPalBlue = setPaletteAndFont(self.qtApp)
self.ui.mainPageSwitcher.setCurrentIndex(0) #1 is messageLabel 0 is the tab widget self.ui.mainPageSwitcher.setCurrentIndex(0) #1 is messageLabel 0 is the tab widget
SettingsDialog.loadFromSettingsAndSendToEngine() #set blacklist, whitelist for programdatabase and addtional executable paths for environment
#TODO: Hide information tab until the feature is ready #TODO: Hide information tab until the feature is ready
self.ui.tabbyCat.removeTab(2) self.ui.tabbyCat.removeTab(2)
self.programIcons = {} #executableName:QIcon. Filled by self.updateProgramDatabase
self.sessionController = SessionController(mainWindow=self) self.sessionController = SessionController(mainWindow=self)
self.systemTray = SystemTray(mainWindow=self) self.systemTray = SystemTray(mainWindow=self)
self.connectMenu() self.connectMenu()
self.recentlyOpenedSessions = RecentlyOpenedSessions() self.recentlyOpenedSessions = RecentlyOpenedSessions()
self.programIcons = {} #executableName:QIcon. Filled by self.updateProgramDatabase
#Menu
self.ui.actionRebuild_Program_Database.triggered.connect(self.updateProgramDatabase)
#Api Callbacks #Api Callbacks
api.callbacks.sessionClosed.append(self.reactCallback_sessionClosed) api.callbacks.sessionClosed.append(self.reactCallback_sessionClosed)
api.callbacks.sessionOpenReady.append(self.reactCallback_sessionOpen) api.callbacks.sessionOpenReady.append(self.reactCallback_sessionOpen)
@ -316,7 +315,6 @@ class MainWindow(QtWidgets.QMainWindow):
self.storeWindowSettings() self.storeWindowSettings()
self._callSysExit() self._callSysExit()
def closeEvent(self, event): def closeEvent(self, event):
"""Window manager close. """Window manager close.
Ignore. We use it to send the GUI into hiding.""" Ignore. We use it to send the GUI into hiding."""
@ -325,9 +323,17 @@ class MainWindow(QtWidgets.QMainWindow):
def connectMenu(self): def connectMenu(self):
#Control #Control
self.ui.actionRebuild_Program_Database.triggered.connect(self.updateProgramDatabase)
self.ui.actionSettings.triggered.connect(self._reactMenu_settings)
self.ui.actionHide_in_System_Tray.triggered.connect(lambda: self.toggleVisible(force=False)) self.ui.actionHide_in_System_Tray.triggered.connect(lambda: self.toggleVisible(force=False))
self.ui.actionMenuQuit.triggered.connect(self.menuRealQuit) self.ui.actionMenuQuit.triggered.connect(self.menuRealQuit)
def _reactMenu_settings(self):
widget = SettingsDialog(self) #blocks until closed
if widget.success:
self.updateProgramDatabase()
def storeWindowSettings(self): def storeWindowSettings(self):
"""Window state is not saved in the real save file. That would lead to portability problems """Window state is not saved in the real save file. That would lead to portability problems
between computers, like different screens and resolutions. between computers, like different screens and resolutions.

BIN
qtgui/resources/translations/de.qm

Binary file not shown.

32
qtgui/resources/translations/de.ts

@ -77,7 +77,6 @@ Für Notizen, TODO, Referenzen, Quellen etc…</translation>
<message> <message>
<location filename="../../designer/mainwindow.py" line="346"/> <location filename="../../designer/mainwindow.py" line="346"/>
<source>Session Name Goes Here</source> <source>Session Name Goes Here</source>
<translatorcomment>Platzhalter</translatorcomment>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
@ -138,55 +137,46 @@ Für Notizen, TODO, Referenzen, Quellen etc…</translation>
<message> <message>
<location filename="../../designer/mainwindow.py" line="362"/> <location filename="../../designer/mainwindow.py" line="362"/>
<source>version and running</source> <source>version and running</source>
<translatorcomment>Platzhalter</translatorcomment>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../../designer/mainwindow.py" line="363"/> <location filename="../../designer/mainwindow.py" line="363"/>
<source>NSM Server Mode</source> <source>NSM Server Mode</source>
<translatorcomment>Platzhalter</translatorcomment>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../../designer/mainwindow.py" line="364"/> <location filename="../../designer/mainwindow.py" line="364"/>
<source>Self-started, connected to, environment var</source> <source>Self-started, connected to, environment var</source>
<translatorcomment>Platzhalter</translatorcomment>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../../designer/mainwindow.py" line="365"/> <location filename="../../designer/mainwindow.py" line="365"/>
<source>NSM Url</source> <source>NSM Url</source>
<translatorcomment>Platzhalter</translatorcomment>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../../designer/mainwindow.py" line="366"/> <location filename="../../designer/mainwindow.py" line="366"/>
<source>osc.upd ip port</source> <source>osc.upd ip port</source>
<translatorcomment>Platzhalter</translatorcomment>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../../designer/mainwindow.py" line="367"/> <location filename="../../designer/mainwindow.py" line="367"/>
<source>Session Root</source> <source>Session Root</source>
<translatorcomment>Platzhalter</translatorcomment>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../../designer/mainwindow.py" line="368"/> <location filename="../../designer/mainwindow.py" line="368"/>
<source>/home/usr/NSM Sessions</source> <source>/home/usr/NSM Sessions</source>
<translatorcomment>Platzhalter</translatorcomment>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../../designer/mainwindow.py" line="369"/> <location filename="../../designer/mainwindow.py" line="369"/>
<source>Program Database</source> <source>Program Database</source>
<translatorcomment>Platzhalter</translatorcomment>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../../designer/mainwindow.py" line="370"/> <location filename="../../designer/mainwindow.py" line="370"/>
<source>Last Updated</source> <source>Last Updated</source>
<translatorcomment>Platzhalter</translatorcomment>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
@ -197,7 +187,6 @@ Für Notizen, TODO, Referenzen, Quellen etc…</translation>
<message> <message>
<location filename="../../designer/mainwindow.py" line="373"/> <location filename="../../designer/mainwindow.py" line="373"/>
<source>Processing</source> <source>Processing</source>
<translatorcomment>Nicht in benutzung</translatorcomment>
<translation>Arbeitet</translation> <translation>Arbeitet</translation>
</message> </message>
<message> <message>
@ -208,13 +197,11 @@ Für Notizen, TODO, Referenzen, Quellen etc…</translation>
<message> <message>
<location filename="../../designer/mainwindow.py" line="375"/> <location filename="../../designer/mainwindow.py" line="375"/>
<source>SessionName</source> <source>SessionName</source>
<translatorcomment>Platzhalter</translatorcomment>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../../designer/mainwindow.py" line="376"/> <location filename="../../designer/mainwindow.py" line="376"/>
<source>ClientNameId</source> <source>ClientNameId</source>
<translatorcomment>Platzhalter</translatorcomment>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
@ -225,7 +212,6 @@ Für Notizen, TODO, Referenzen, Quellen etc…</translation>
<message> <message>
<location filename="../../designer/mainwindow.py" line="378"/> <location filename="../../designer/mainwindow.py" line="378"/>
<source>Ctrl+Shift+Q</source> <source>Ctrl+Shift+Q</source>
<translatorcomment>Keine Shortcuts übersetzen</translatorcomment>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
@ -246,7 +232,6 @@ Für Notizen, TODO, Referenzen, Quellen etc…</translation>
<message> <message>
<location filename="../../designer/mainwindow.py" line="382"/> <location filename="../../designer/mainwindow.py" line="382"/>
<source>Ctrl+Q</source> <source>Ctrl+Q</source>
<translatorcomment>Keine Shortcuts übersetzen</translatorcomment>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
@ -257,13 +242,11 @@ Für Notizen, TODO, Referenzen, Quellen etc…</translation>
<message> <message>
<location filename="../../designer/mainwindow.py" line="384"/> <location filename="../../designer/mainwindow.py" line="384"/>
<source>A</source> <source>A</source>
<translatorcomment>Keine Shortcuts übersetzen</translatorcomment>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../../designer/mainwindow.py" line="386"/> <location filename="../../designer/mainwindow.py" line="386"/>
<source>Ctrl+S</source> <source>Ctrl+S</source>
<translatorcomment>Keine Shortcuts übersetzen</translatorcomment>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
@ -274,13 +257,11 @@ Für Notizen, TODO, Referenzen, Quellen etc…</translation>
<message> <message>
<location filename="../../designer/mainwindow.py" line="388"/> <location filename="../../designer/mainwindow.py" line="388"/>
<source>Ctrl+Shift+S</source> <source>Ctrl+Shift+S</source>
<translatorcomment>Keine Shortcuts übersetzen</translatorcomment>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../../designer/mainwindow.py" line="390"/> <location filename="../../designer/mainwindow.py" line="390"/>
<source>Ctrl+W</source> <source>Ctrl+W</source>
<translatorcomment>Keine Shortcuts übersetzen</translatorcomment>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
@ -291,7 +272,6 @@ Für Notizen, TODO, Referenzen, Quellen etc…</translation>
<message> <message>
<location filename="../../designer/mainwindow.py" line="392"/> <location filename="../../designer/mainwindow.py" line="392"/>
<source>Ctrl+Shift+W</source> <source>Ctrl+Shift+W</source>
<translatorcomment>Keine Shortcuts übersetzen</translatorcomment>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
@ -302,7 +282,6 @@ Für Notizen, TODO, Referenzen, Quellen etc…</translation>
<message> <message>
<location filename="../../designer/mainwindow.py" line="394"/> <location filename="../../designer/mainwindow.py" line="394"/>
<source>Alt+O</source> <source>Alt+O</source>
<translatorcomment>Keine Shortcuts übersetzen</translatorcomment>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
@ -313,7 +292,6 @@ Für Notizen, TODO, Referenzen, Quellen etc…</translation>
<message> <message>
<location filename="../../designer/mainwindow.py" line="396"/> <location filename="../../designer/mainwindow.py" line="396"/>
<source>Alt+R</source> <source>Alt+R</source>
<translatorcomment>Keine Shortcuts übersetzen</translatorcomment>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
@ -324,7 +302,6 @@ Für Notizen, TODO, Referenzen, Quellen etc…</translation>
<message> <message>
<location filename="../../designer/mainwindow.py" line="398"/> <location filename="../../designer/mainwindow.py" line="398"/>
<source>Alt+S</source> <source>Alt+S</source>
<translatorcomment>Keine Shortcuts übersetzen</translatorcomment>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
@ -335,7 +312,6 @@ Für Notizen, TODO, Referenzen, Quellen etc…</translation>
<message> <message>
<location filename="../../designer/mainwindow.py" line="400"/> <location filename="../../designer/mainwindow.py" line="400"/>
<source>Alt+X</source> <source>Alt+X</source>
<translatorcomment>Keine Shortcuts übersetzen</translatorcomment>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
@ -346,7 +322,6 @@ Für Notizen, TODO, Referenzen, Quellen etc…</translation>
<message> <message>
<location filename="../../designer/mainwindow.py" line="402"/> <location filename="../../designer/mainwindow.py" line="402"/>
<source>Alt+T</source> <source>Alt+T</source>
<translatorcomment>Keine Shortcuts übersetzen</translatorcomment>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
@ -372,7 +347,6 @@ Für Notizen, TODO, Referenzen, Quellen etc…</translation>
<message> <message>
<location filename="../../designer/mainwindow.py" line="407"/> <location filename="../../designer/mainwindow.py" line="407"/>
<source>F2</source> <source>F2</source>
<translatorcomment>Keine Shortcuts übersetzen</translatorcomment>
<translation></translation> <translation></translation>
</message> </message>
</context> </context>
@ -381,7 +355,6 @@ Für Notizen, TODO, Referenzen, Quellen etc…</translation>
<message> <message>
<location filename="../../designer/newsession.py" line="47"/> <location filename="../../designer/newsession.py" line="47"/>
<source>Dialog</source> <source>Dialog</source>
<translatorcomment>Platzhalter</translatorcomment>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
@ -392,8 +365,8 @@ Für Notizen, TODO, Referenzen, Quellen etc…</translation>
<message> <message>
<location filename="../../designer/newsession.py" line="49"/> <location filename="../../designer/newsession.py" line="49"/>
<source>Save JACK Connections <source>Save JACK Connections
(adds clients &apos;nsm-jack&apos;)</source> (adds clients &apos;jackpatch&apos;)</source>
<translation>Speichere JACK-Verbindungen (mit Client &apos;nsm-jack&apos;)</translation> <translation>Speichere JACK-Verbindungen (mit Client &apos;jackpatch&apos;)</translation>
</message> </message>
<message> <message>
<location filename="../../designer/newsession.py" line="51"/> <location filename="../../designer/newsession.py" line="51"/>
@ -470,7 +443,6 @@ Für Notizen, TODO, Referenzen, Quellen etc…</translation>
<message> <message>
<location filename="../../designer/projectname.py" line="43"/> <location filename="../../designer/projectname.py" line="43"/>
<source>Form</source> <source>Form</source>
<translatorcomment>Platzhalter</translatorcomment>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>

116
qtgui/settings.py

@ -0,0 +1,116 @@
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Copyright 2020, Nils Hilbricht, Germany ( https://www.hilbricht.net )
This file is part of the Laborejo Software Suite ( https://www.laborejo.org ),
This application 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
import pathlib
import os
#Third Party
from PyQt5 import QtCore, QtWidgets
#Engine
import engine.api as api
from engine.config import METADATA #includes METADATA only. No other environmental setup is executed.
#QtGui
from .designer.settings import Ui_Dialog
class SettingsDialog(QtWidgets.QDialog):
def __init__(self, mainWindow):
super().__init__(mainWindow)
self.ui = Ui_Dialog()
self.ui.setupUi(self)
self.mainWindow = mainWindow
self.success = False
settings = QtCore.QSettings("LaborejoSoftwareSuite", METADATA["shortName"])
if settings.contains("launcherBlacklistPlainTextEdit"):
self.ui.launcherBlacklistPlainTextEdit.setPlainText(settings.value("launcherBlacklistPlainTextEdit", type=str))
else:
self.ui.launcherBlacklistPlainTextEdit.setPlainText("")
if settings.contains("launcherWhitelistPlainTextEdit"):
self.ui.launcherWhitelistPlainTextEdit.setPlainText(settings.value("launcherWhitelistPlainTextEdit", type=str))
else:
self.ui.launcherWhitelistPlainTextEdit.setPlainText("")
if settings.contains("programPathsPlainTextEdit"):
self.ui.programPathsPlainTextEdit.setPlainText(settings.value("programPathsPlainTextEdit", type=str))
else:
self.ui.programPathsPlainTextEdit.setPlainText("")
#self.ui.name.textEdited.connect(self.check) #not called when text is changed programatically
self.ui.buttonBox.accepted.connect(self.process)
self.ui.buttonBox.rejected.connect(self.reject)
self.setWindowFlag(QtCore.Qt.Popup, True)
self.setModal(True)
self.setFocus(True)
self.exec_()
@staticmethod
def loadFromSettingsAndSendToEngine():
"""Called on program start and in self.process, which has a bit overhead because
it is saving to file and then reloading from file (qsettings)"""
settings = QtCore.QSettings("LaborejoSoftwareSuite", METADATA["shortName"])
if settings.contains("launcherBlacklistPlainTextEdit"):
bl = settings.value("launcherBlacklistPlainTextEdit", type=str)
else:
bl = None
if settings.contains("launcherWhitelistPlainTextEdit"):
wl = settings.value("launcherWhitelistPlainTextEdit", type=str)
else:
wl = None
if settings.contains("programPathsPlainTextEdit"):
pth = settings.value("programPathsPlainTextEdit", type=str)
else:
pth = None
blacklist = bl.split("\n") if bl else []
whitelist = wl.split("\n") if wl else []
api.systemProgramsSetBlacklist(blacklist)
api.systemProgramsSetWhitelist(whitelist)
#Depends on SettingsDialog: More executable paths for the engine. We do this in mainwindow because it has access to the qsettings safe file and is started before engine, program-database or nsmd.
additionalExecutablePaths = pth.split("\n") if pth else []
if additionalExecutablePaths:
os.environ["PATH"] = os.pathsep.join(additionalExecutablePaths) + os.pathsep + os.environ["PATH"]
def process(self):
settings = QtCore.QSettings("LaborejoSoftwareSuite", METADATA["shortName"])
settings.setValue("launcherBlacklistPlainTextEdit", self.ui.launcherBlacklistPlainTextEdit.toPlainText())
settings.setValue("launcherWhitelistPlainTextEdit", self.ui.launcherWhitelistPlainTextEdit.toPlainText())
settings.setValue("programPathsPlainTextEdit", self.ui.programPathsPlainTextEdit.toPlainText())
SettingsDialog.loadFromSettingsAndSendToEngine()
self.success = True
self.done(True)
Loading…
Cancel
Save