Music production session manager
https://www.laborejo.org
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.
520 lines
24 KiB
520 lines
24 KiB
#! /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
|
|
|
|
#Third Party
|
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
|
|
|
#Engine
|
|
import engine.api as api
|
|
import engine.findicons as findicons
|
|
|
|
#Qt
|
|
from .descriptiontextwidget import DescriptionController
|
|
from .helper import iconFromString
|
|
|
|
iconSize = QtCore.QSize(16,16)
|
|
|
|
class ClientItem(QtWidgets.QTreeWidgetItem):
|
|
"""
|
|
Item on the right side. Clients of the session, in various states.
|
|
|
|
|
|
clientDict = {
|
|
"clientId":clientId, #for convenience, included internally as well
|
|
"dumbClient":True, #Bool. Real nsm or just any old program? status "Ready" switches this.
|
|
"reportedName":None, #str
|
|
"label":None, #str
|
|
"lastStatus":None, #str
|
|
"statusHistory":[], #list
|
|
"hasOptionalGUI": False, #bool
|
|
"visible": None, # bool
|
|
"dirty": None, # bool
|
|
}
|
|
"""
|
|
|
|
allItems = {} # clientId : ClientItem
|
|
|
|
def __init__(self, parentController, clientDict:dict):
|
|
ClientItem.allItems[clientDict["clientId"]] = self
|
|
self.parentController = parentController
|
|
self.clientDict = clientDict
|
|
parameterList = [] #later in update
|
|
super().__init__(parameterList, type=1000) #type 0 is default qt type. 1000 is subclassed user type)
|
|
self.defaultFlags = self.flags()
|
|
self.setFlags(self.defaultFlags | QtCore.Qt.ItemIsEditable) #We have editTrigger to none so we can explicitly allow to only edit the name column on menuAction
|
|
#self.treeWidget() not ready at this point
|
|
self.updateData(clientDict)
|
|
self.updateIcon(clientDict)
|
|
|
|
def dataClientNameOverride(self, name:str):
|
|
"""Either string or None. If None we reset to nsmd name"""
|
|
logger.info(f"Custom name for id {self.clientDict['clientId']} {self.clientDict['reportedName']}: {name}")
|
|
if name:
|
|
text = name
|
|
else:
|
|
text = self.clientDict["reportedName"]
|
|
|
|
index = self.parentController.clientsTreeWidgetColumns.index("reportedName")
|
|
self.setText(index, text)
|
|
|
|
def updateData(self, clientDict:dict):
|
|
"""Arrives via parenTreeWidget api callback statusChanged, which is nsm status changed"""
|
|
self.clientDict = clientDict
|
|
for index, key in enumerate(self.parentController.clientsTreeWidgetColumns):
|
|
if clientDict[key] is None:
|
|
t = ""
|
|
else:
|
|
value = clientDict[key]
|
|
if key == "visible":
|
|
if value == True:
|
|
t = "✔"
|
|
else:
|
|
t = "✖"
|
|
elif key == "dirty":
|
|
if value == True:
|
|
t = QtCore.QCoreApplication.translate("OpenSession", "not saved")
|
|
else:
|
|
t = QtCore.QCoreApplication.translate("OpenSession", "clean")
|
|
elif key == "reportedName" and self.parentController.clientOverrideNamesCache:
|
|
if clientDict["clientId"] in self.parentController.clientOverrideNamesCache:
|
|
t = self.parentController.clientOverrideNamesCache[clientDict["clientId"]]
|
|
logger.info(f"Update Data: custom name for id {self.clientDict['clientId']} {self.clientDict['reportedName']}: {t}")
|
|
else:
|
|
t = str(value)
|
|
else:
|
|
t = str(value)
|
|
self.setText(index, t)
|
|
|
|
nameColumn = self.parentController.clientsTreeWidgetColumns.index("reportedName")
|
|
if clientDict["reportedName"] is None:
|
|
self.setText(nameColumn, clientDict["executable"])
|
|
|
|
def updateIcon(self, clientDict:dict):
|
|
"""Just called during init"""
|
|
programIcons = self.parentController.mainWindow.programIcons
|
|
assert programIcons
|
|
assert "executable" in clientDict, clientDict
|
|
iconColumn = self.parentController.clientsTreeWidgetColumns.index("reportedName")
|
|
if clientDict["executable"] in programIcons:
|
|
icon = programIcons[clientDict["executable"]]
|
|
assert icon, icon
|
|
self.setIcon(iconColumn, icon) #reported name is correct here. this is just the column.
|
|
else: #Not NSM client added by the prompt widget
|
|
result = findicons.findIconPath(clientDict["executable"])
|
|
if result:
|
|
icon = QtGui.QIcon.fromTheme(str(result[0]))
|
|
else:
|
|
icon = QtGui.QIcon.fromTheme(clientDict["executable"])
|
|
|
|
if not icon.isNull():
|
|
self.setIcon(iconColumn, icon)
|
|
else:
|
|
self.setIcon(iconColumn, iconFromString(clientDict["executable"]))
|
|
|
|
class ClientTable(object):
|
|
"""Controls the QTreeWidget that holds loaded clients"""
|
|
|
|
def __init__(self, mainWindow, parent):
|
|
self.mainWindow = mainWindow
|
|
self.parent = parent
|
|
|
|
self.clientOverrideNamesCache = None #None or dict. Dict is never truly empty, it has at least empty categories.
|
|
|
|
self.sortByColumnValue = 0 #by name
|
|
self.sortDescendingValue = 0 # Qt::SortOrder which is 0 for ascending and 1 for descending
|
|
self.clientsTreeWidget = self.mainWindow.ui.loadedSessionClients
|
|
|
|
self.clientsTreeWidget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
|
self.clientsTreeWidget.customContextMenuRequested.connect(self.clientsContextMenu)
|
|
|
|
self.clientsTreeWidget.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
|
|
self.clientsTreeWidget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
|
|
|
|
self.clientsTreeWidget.setIconSize(iconSize)
|
|
self.clientsTreeWidget.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) #We only allow explicit editing.
|
|
self.clientsTreeWidgetColumns = ("reportedName", "label", "lastStatus", "visible", "dirty", "clientId") #basically an enum
|
|
self.clientHeaderLabels = [
|
|
QtCore.QCoreApplication.translate("OpenSession", "Name"),
|
|
QtCore.QCoreApplication.translate("OpenSession", "Label"),
|
|
QtCore.QCoreApplication.translate("OpenSession", "Status"),
|
|
QtCore.QCoreApplication.translate("OpenSession", "Visible"),
|
|
QtCore.QCoreApplication.translate("OpenSession", "Changes"),
|
|
QtCore.QCoreApplication.translate("OpenSession", "ID"),
|
|
]
|
|
self.clientsTreeWidget.setHeaderLabels(self.clientHeaderLabels)
|
|
self.clientsTreeWidget.setSortingEnabled(True)
|
|
self.clientsTreeWidget.setAlternatingRowColors(True)
|
|
|
|
#Signals
|
|
self.clientsTreeWidget.currentItemChanged.connect(self._reactSignal_currentClientChanged)
|
|
self.clientsTreeWidget.itemDoubleClicked.connect(self._reactSignal_itemDoubleClicked) #This is hide/show and NOT edit
|
|
self.clientsTreeWidget.itemDelegate().closeEditor.connect(self._reactSignal_itemEditingFinished)
|
|
self.clientsTreeWidget.model().layoutAboutToBeChanged.connect(self._reactSignal_rememberSorting)
|
|
#self.clientsTreeWidget.model().layoutChanged.connect(self._reactSignal_restoreSorting)
|
|
#Convenience Signals to directly disable the client messages on gui instruction.
|
|
#This is purely for speed and preventing the user from sending a signal while the session is shutting down
|
|
self.mainWindow.ui.actionSessionAbort.triggered.connect(lambda: self._updateClientMenu(deactivate=True))
|
|
self.mainWindow.ui.actionSessionSaveAndClose.triggered.connect(lambda: self._updateClientMenu(deactivate=True))
|
|
|
|
#API Callbacks
|
|
api.callbacks.sessionOpenLoading.append(self._cleanClients)
|
|
api.callbacks.sessionOpenReady.append(self._updateClientMenu)
|
|
api.callbacks.sessionClosed.append(lambda: self._updateClientMenu(deactivate=True))
|
|
api.callbacks.clientStatusChanged.append(self._reactCallback_clientStatusChanged)
|
|
api.callbacks.dataClientNamesChanged.append(self._reactCallback_dataClientNamesChanged)
|
|
|
|
def _adjustColumnSize(self):
|
|
self.clientsTreeWidget.sortItems(self.sortByColumnValue, self.sortDescendingValue)
|
|
for index in range(self.clientsTreeWidget.columnCount()):
|
|
self.clientsTreeWidget.resizeColumnToContents(index)
|
|
|
|
def _cleanClients(self, nsmSessionExportDict:dict):
|
|
"""Reset everything to the initial, empty state.
|
|
We do not reset in in openReady because that signifies that the session is ready.
|
|
And not in session closed because we want to setup data structures."""
|
|
ClientItem.allItems.clear()
|
|
self.clientsTreeWidget.clear()
|
|
|
|
def clientsContextMenu(self, qpoint):
|
|
"""Reuses the menubar menus"""
|
|
pos = QtGui.QCursor.pos()
|
|
pos.setY(pos.y() + 5)
|
|
|
|
item = self.clientsTreeWidget.itemAt(qpoint)
|
|
if not type(item) is ClientItem:
|
|
self.mainWindow.ui.menuSession.exec_(pos)
|
|
return
|
|
|
|
if not item is self.clientsTreeWidget.currentItem():
|
|
#Some mouse combinations can lead to getting a different context menu than the clicked item.
|
|
self.clientsTreeWidget.setCurrentItem(item)
|
|
|
|
menu = self.mainWindow.ui.menuClientNameId
|
|
menu.exec_(pos)
|
|
|
|
|
|
def _startEditingName(self, *args):
|
|
currentItem = self.clientsTreeWidget.currentItem()
|
|
self.editableItem = currentItem
|
|
column = self.clientsTreeWidgetColumns.index("reportedName")
|
|
self.clientsTreeWidget.editItem(currentItem, column)
|
|
|
|
def _reactSignal_itemEditingFinished(self, qLineEdit, returnCode):
|
|
"""This is a hacky signal. It arrives every change, programatically or manually.
|
|
We therefore only connect this signal right after a double click and disconnect it
|
|
afterwards.
|
|
And we still need to block signals while this is running.
|
|
|
|
returnCode: no clue? Integers all over the place...
|
|
"""
|
|
treeWidgetItem = self.editableItem
|
|
self.editableItem = None
|
|
self.clientsTreeWidget.blockSignals(True)
|
|
if treeWidgetItem:
|
|
#We send the signal directly. Updating is done via callback.
|
|
newName = treeWidgetItem.text(0)
|
|
if not newName == treeWidgetItem.clientDict["reportedName"]:
|
|
api.clientNameOverride(treeWidgetItem.clientDict["clientId"], newName)
|
|
self.clientsTreeWidget.blockSignals(False)
|
|
|
|
def _reactSignal_currentClientChanged(self, treeWidgetItem, previousItem):
|
|
"""Cache the current id for the client menu and shortcuts"""
|
|
if treeWidgetItem:
|
|
self.currentClientId = treeWidgetItem.clientDict["clientId"]
|
|
else:
|
|
self.currentClientId = None
|
|
self._updateClientMenu()
|
|
|
|
def _reactSignal_itemDoubleClicked(self, item:QtWidgets.QTreeWidgetItem, column:int):
|
|
if item.clientDict["hasOptionalGUI"]:
|
|
api.clientToggleVisible(item.clientDict["clientId"])
|
|
|
|
def _reactCallback_clientStatusChanged(self, clientDict:dict):
|
|
"""The major client callback. Maps to nsmd status changes.
|
|
We will create and delete client tableWidgetItems based on this
|
|
"""
|
|
assert clientDict
|
|
clientId = clientDict["clientId"]
|
|
if clientId in ClientItem.allItems:
|
|
if clientDict["lastStatus"] == "removed":
|
|
index = self.clientsTreeWidget.indexOfTopLevelItem(ClientItem.allItems[clientId])
|
|
self.clientsTreeWidget.takeTopLevelItem(index)
|
|
del ClientItem.allItems[clientId]
|
|
else:
|
|
ClientItem.allItems[clientId].updateData(clientDict)
|
|
self._updateClientMenu() #Update here is fine because shutdown sets to status removed.
|
|
else:
|
|
#Create new. Item will be parented by Qt, so Python GC will not delete
|
|
item = ClientItem(parentController=self, clientDict=clientDict)
|
|
self.clientsTreeWidget.addTopLevelItem(item)
|
|
|
|
self._adjustColumnSize()
|
|
|
|
#Do not put a general menuUpdate here. It will re-open the client menu during shutdown, enabling the user to send false commands to the client.
|
|
|
|
|
|
def _reactCallback_dataClientNamesChanged(self, clientOverrideNames:dict):
|
|
"""We either expect a dict or None. If None we return after clearing the data.
|
|
We clear every callback and re-build.
|
|
|
|
The dict can be content-empty of course."""
|
|
logger.info(f"Received dataStorage names update: {clientOverrideNames}")
|
|
|
|
#Clear current GUI data.
|
|
for clientInstance in ClientItem.allItems.values():
|
|
clientInstance.dataClientNameOverride(None)
|
|
|
|
if clientOverrideNames is None: #This only happens if there was a client present and that exits.
|
|
self.clientOverrideNamesCache = None
|
|
else:
|
|
#Real data
|
|
#assert "origin" in data, data . Not in a fresh session, after adding!
|
|
#assert data["origin"] == "https://www.laborejo.org/agordejo/nsm-data", data["origin"]
|
|
self.clientOverrideNamesCache = clientOverrideNames #Can be empty dict as well
|
|
clients = ClientItem.allItems
|
|
for clientId, name in clientOverrideNames.items():
|
|
#It is possible on session start, that a client has not yet loaded but we already receive a name override. nsm-data is instructed to only announce after session has loaded, but that can go wrong when nsmd has a bad day.
|
|
#Long story short: better to not rename right now, have some name mismatch and wait for a general update later, which will happen after every client load anyway.
|
|
if clientId in clients:
|
|
clients[clientId].dataClientNameOverride(name)
|
|
|
|
self._updateClientMenu() #Update because we need to en/disable the rename action
|
|
self._adjustColumnSize()
|
|
|
|
def _updateClientMenu(self, deactivate=False):
|
|
"""The client menu changes with every currentItem edit to reflect the name and capabilities"""
|
|
|
|
ui = self.mainWindow.ui
|
|
menu = ui.menuClientNameId
|
|
|
|
if deactivate:
|
|
currentItem = None
|
|
else:
|
|
currentItem = self.clientsTreeWidget.currentItem()
|
|
|
|
if currentItem:
|
|
clientId = currentItem.clientDict["clientId"]
|
|
state = True
|
|
#if currentItem.clientDict["label"]:
|
|
# name = currentItem.clientDict["label"]
|
|
#else:
|
|
# name = currentItem.clientDict["reportedName"]
|
|
name = currentItem.text(self.clientsTreeWidgetColumns.index("reportedName"))
|
|
else:
|
|
state = False
|
|
name = "Client"
|
|
|
|
menu.setTitle(name)
|
|
#menu.setEnabled(state) #It is not enough to disable the menu itself. Shortcuts will still work.
|
|
for action in menu.actions():
|
|
action.setEnabled(state)
|
|
|
|
if state:
|
|
ui.actionClientRename.triggered.disconnect()
|
|
ui.actionClientRename.triggered.connect(self._startEditingName)
|
|
#ui.actionClientRename.triggered.connect(lambda: self.clientsTreeWidget.editItem(currentItem, self.clientsTreeWidgetColumns.index("reportedName")))
|
|
ui.actionClientSave_separately.triggered.disconnect()
|
|
ui.actionClientSave_separately.triggered.connect(lambda: api.clientSave(clientId))
|
|
ui.actionClientStop.triggered.disconnect()
|
|
ui.actionClientStop.triggered.connect(lambda: api.clientStop(clientId))
|
|
#ui.actionClientStop.triggered.connect(self._updateClientMenu) #too early. We need to wait for the status callback
|
|
ui.actionClientResume.triggered.disconnect()
|
|
ui.actionClientResume.triggered.connect(lambda: api.clientResume(clientId))
|
|
#ui.actionClientResume.triggered.connect(self._updateClientMenu) #too early. We need to wait for the status callback
|
|
ui.actionClientRemove.triggered.disconnect()
|
|
ui.actionClientRemove.triggered.connect(lambda: api.clientRemove(clientId))
|
|
|
|
#Deactivate depending on the state of the program
|
|
if currentItem.clientDict["lastStatus"] == "stopped":
|
|
ui.actionClientSave_separately.setEnabled(False)
|
|
ui.actionClientStop.setEnabled(False)
|
|
ui.actionClientToggleVisible.setEnabled(False)
|
|
ui.actionClientResume.setEnabled(True)
|
|
else:
|
|
ui.actionClientResume.setEnabled(False)
|
|
#Hide and show shall only be enabled and connected if supported by the client
|
|
try:
|
|
ui.actionClientToggleVisible.triggered.disconnect()
|
|
except TypeError: #TypeError: disconnect() failed between 'triggered' and all its connections
|
|
pass
|
|
if currentItem.clientDict["hasOptionalGUI"]:
|
|
ui.actionClientToggleVisible.setEnabled(True)
|
|
ui.actionClientToggleVisible.triggered.connect(lambda: api.clientToggleVisible(clientId))
|
|
else:
|
|
ui.actionClientToggleVisible.setEnabled(False)
|
|
|
|
#Only rename when dataclient is present
|
|
#None or dict, even empty dict
|
|
if self.clientOverrideNamesCache is None:
|
|
ui.actionClientRename.setEnabled(False)
|
|
else:
|
|
ui.actionClientRename.setEnabled(True)
|
|
|
|
def _reactSignal_rememberSorting(self, *args):
|
|
self.sortByColumnValue = self.clientsTreeWidget.header().sortIndicatorSection()
|
|
self.sortDescendingValue = self.clientsTreeWidget.header().sortIndicatorOrder()
|
|
|
|
def _reactSignal_restoreSorting(self, *args):
|
|
"""Do not use as signal!!! Will lead to infinite recursion since Qt 5.12.2"""
|
|
#self.clientsTreeWidget.sortItems(self.sortByColumnValue, self.sortDescendingValue)
|
|
raise RuntimeError()
|
|
|
|
class LauncherProgram(QtWidgets.QTreeWidgetItem):
|
|
"""
|
|
An item on the left side of the window. Used to start programs and show info, but nothing more.
|
|
|
|
Example:
|
|
{ 'categories': 'AudioVideo;Audio;X-Recorders;X-Multitrack;X-Jack;',
|
|
'comment': 'Easy to use pattern sequencer for JACK and NSM',
|
|
'comment[de]': 'Einfach zu bedienender Pattern-Sequencer',
|
|
'exec': 'patroneo',
|
|
'genericname': 'Sequencer',
|
|
'icon': 'patroneo',
|
|
'name': 'Patroneo',
|
|
'startupnotify': 'false',
|
|
'terminal': 'false',
|
|
'type': 'Application',
|
|
'version': '1.0', #desktop spec version, not progra,
|
|
'x-nsm-capable': 'true'}
|
|
|
|
Also:
|
|
'agordejoExec' : the actual nsm exe
|
|
'agordejoIconPath' : a priority path the engine found for us
|
|
|
|
"""
|
|
|
|
allItems = {} # clientId : ClientItem
|
|
|
|
def __init__(self, parentController, launcherDict:dict):
|
|
LauncherProgram.allItems[launcherDict["agordejoExec"]] = self
|
|
self.parentController = parentController
|
|
self.launcherDict = launcherDict
|
|
self.executable = launcherDict["agordejoExec"]
|
|
|
|
|
|
|
|
parameterList = [] #later in update
|
|
super().__init__(parameterList, type=1000) #type 0 is default qt type. 1000 is subclassed user type)
|
|
self.updateData(launcherDict)
|
|
|
|
def updateData(self, launcherDict:dict):
|
|
"""Arrives via parenTreeWidget api callback"""
|
|
self.launcherDict = launcherDict
|
|
|
|
for index, key in enumerate(self.parentController.columns):
|
|
if (not key in launcherDict) or launcherDict[key] is None:
|
|
t = ""
|
|
else:
|
|
t = str(launcherDict[key])
|
|
self.setText(index, t)
|
|
|
|
programIcons = self.parentController.mainWindow.programIcons
|
|
assert programIcons
|
|
if launcherDict["agordejoExec"] in programIcons:
|
|
icon = programIcons[launcherDict["agordejoExec"]]
|
|
self.setIcon(self.parentController.columns.index("name"), icon) #name is correct here. this is just the column.
|
|
|
|
class LauncherTable(object):
|
|
"""Controls the QTreeWidget that holds programs in the PATH.
|
|
"""
|
|
def __init__(self, mainWindow, parent):
|
|
self.mainWindow = mainWindow
|
|
self.parent = parent
|
|
|
|
self.sortByColumnValue = 0 # by name
|
|
self.sortDescendingValue = 0 # Qt::SortOrder which is 0 for ascending and 1 for descending
|
|
self.launcherWidget = self.mainWindow.ui.loadedSessionsLauncher
|
|
self.launcherWidget.setIconSize(iconSize)
|
|
|
|
self.columns = ("name", "comment", "agordejoFullPath") #basically an enum
|
|
self.headerLables = [
|
|
QtCore.QCoreApplication.translate("Launcher", "Name"),
|
|
QtCore.QCoreApplication.translate("Launcher", "Description"),
|
|
QtCore.QCoreApplication.translate("Launcher", "Path"),
|
|
]
|
|
self.launcherWidget.setHeaderLabels(self.headerLables)
|
|
self.launcherWidget.setSortingEnabled(True)
|
|
self.launcherWidget.setAlternatingRowColors(True)
|
|
|
|
self.launcherWidget.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
|
|
self.launcherWidget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
|
|
|
|
##The actual program entries are handled by the LauncherProgram item class
|
|
#self.buildPrograms() #Don't call here. MainWindow calls it when everything is ready.
|
|
|
|
#Signals
|
|
self.launcherWidget.itemDoubleClicked.connect(self._reactSignal_launcherItemDoubleClicked)
|
|
|
|
|
|
def _adjustColumnSize(self):
|
|
self.launcherWidget.sortItems(self.sortByColumnValue, self.sortDescendingValue)
|
|
for index in range(self.launcherWidget.columnCount()):
|
|
self.launcherWidget.resizeColumnToContents(index)
|
|
|
|
def _reactSignal_launcherItemDoubleClicked(self, item):
|
|
api.clientAdd(item.executable)
|
|
|
|
def buildPrograms(self):
|
|
"""Called by mainWindow.updateProgramDatabase
|
|
|
|
Receive entries from the engine.
|
|
Entry is a dict modelled after a .desktop file.
|
|
But not all entries have all data. Some are barebones executable name and path.
|
|
|
|
Only guaranteed keys are agordejoExec and agordejoFullPath, which in turn are files
|
|
guaranteed to exist in the path.
|
|
"""
|
|
self.launcherWidget.clear()
|
|
engineCache = api.getCache()
|
|
programs = engineCache["programs"]
|
|
|
|
for entry in programs:
|
|
item = LauncherProgram(parentController=self, launcherDict=entry)
|
|
self.launcherWidget.addTopLevelItem(item)
|
|
|
|
self._adjustColumnSize()
|
|
|
|
class OpenSessionController(object):
|
|
"""Not a subclass. Controls the visible tab, when a session is open.
|
|
There is only one open instance at a time that controls the GUI and cleans itself."""
|
|
|
|
def __init__(self, mainWindow):
|
|
self.mainWindow = mainWindow
|
|
self.clientTabe = ClientTable(mainWindow=mainWindow, parent=self)
|
|
self.launcherTable = LauncherTable(mainWindow=mainWindow, parent=self)
|
|
|
|
self.descriptionController = DescriptionController(mainWindow, self.mainWindow.ui.loadedSessionDescriptionGroupBox, self.mainWindow.ui.loadedSessionDescription)
|
|
|
|
self.sessionLoadedPanel = mainWindow.ui.session_loaded #groupbox
|
|
self.sessionProgramsPanel = mainWindow.ui.session_programs #groupbox
|
|
|
|
#API Callbacks
|
|
api.callbacks.sessionOpenReady.append(self._reactCallback_sessionOpen)
|
|
logger.info("Full View Open Session Controller ready")
|
|
|
|
def _reactCallback_sessionOpen(self, nsmSessionExportDict:dict):
|
|
"""Open does not mean we come from the session chooser. Switching does not close a session"""
|
|
#self.description.clear() #Deletes the placesholder and text!
|
|
self.sessionLoadedPanel.setTitle(nsmSessionExportDict["nsmSessionName"])
|
|
|