Browse Source

Remove quick mode from program

tags/v0.3.0
Nils 2 months ago
parent
commit
95a71d9b91
7 changed files with 13 additions and 753 deletions
  1. +4
    -0
      CHANGELOG
  2. +0
    -3
      engine/config.py
  3. +3
    -96
      qtgui/designer/mainwindow.py
  4. +0
    -180
      qtgui/designer/mainwindow.ui
  5. +6
    -15
      qtgui/mainwindow.py
  6. +0
    -332
      qtgui/quickopensessioncontroller.py
  7. +0
    -127
      qtgui/quicksessioncontroller.py

+ 4
- 0
CHANGELOG View File

@@ -1,3 +1,7 @@
2021-07-08 Version 0.3
Remove "Quick" mode. As it turns out "Full" mode is quick enough. Port convenience features to full mode.


2021-01-15 Version 0.2.1
Remove Nuitka as dependency. Build commands stay the same.


+ 0
- 3
engine/config.py View File

@@ -44,9 +44,6 @@ Agordejo (Esperanto: 'place to set things up') is a music production session man
It is used to start your programs, remember their (JACK) interconnections and make your life easier
in general.
""" + "\n" + """
You can seamlessly change between two view modes to quickly start a few programs or have complete
control and a detailed overview.
""" + "\n" + """
Agordejo does not re-invent the wheel but instead uses the New-Session-Manager daemon and enhances
it with some tricks of its own, that always remain 100% compatible with the original sessions.
""" + "\n" + """

+ 3
- 96
qtgui/designer/mainwindow.py View File

@@ -2,7 +2,7 @@

# Form implementation generated from reading ui file 'mainwindow.ui'
#
# Created by: PyQt5 UI code generator 5.15.0
# Created by: PyQt5 UI code generator 5.15.4
#
# 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.
@@ -30,91 +30,6 @@ class Ui_MainWindow(object):
self.verticalLayout_12.setObjectName("verticalLayout_12")
self.tabbyCat = QtWidgets.QTabWidget(self.tabPage)
self.tabbyCat.setObjectName("tabbyCat")
self.tab_quick = QtWidgets.QWidget()
self.tab_quick.setObjectName("tab_quick")
self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.tab_quick)
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.quickStackedWidget = QtWidgets.QStackedWidget(self.tab_quick)
self.quickStackedWidget.setObjectName("quickStackedWidget")
self.page_quickSessionChooser = QtWidgets.QWidget()
self.page_quickSessionChooser.setObjectName("page_quickSessionChooser")
self.verticalLayout_9 = QtWidgets.QVBoxLayout(self.page_quickSessionChooser)
self.verticalLayout_9.setObjectName("verticalLayout_9")
self.quickNewSession = QtWidgets.QPushButton(self.page_quickSessionChooser)
self.quickNewSession.setAutoDefault(True)
self.quickNewSession.setDefault(True)
self.quickNewSession.setObjectName("quickNewSession")
self.verticalLayout_9.addWidget(self.quickNewSession)
self.scrollArea = QtWidgets.QScrollArea(self.page_quickSessionChooser)
self.scrollArea.setWidgetResizable(True)
self.scrollArea.setAlignment(QtCore.Qt.AlignCenter)
self.scrollArea.setObjectName("scrollArea")
self.quickSessionChooser = QtWidgets.QWidget()
self.quickSessionChooser.setGeometry(QtCore.QRect(0, 0, 893, 607))
self.quickSessionChooser.setObjectName("quickSessionChooser")
self.verticalLayout_7 = QtWidgets.QVBoxLayout(self.quickSessionChooser)
self.verticalLayout_7.setObjectName("verticalLayout_7")
spacerItem = QtWidgets.QSpacerItem(20, 586, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout_7.addItem(spacerItem)
self.scrollArea.setWidget(self.quickSessionChooser)
self.verticalLayout_9.addWidget(self.scrollArea)
self.quickStackedWidget.addWidget(self.page_quickSessionChooser)
self.page_quickSessionLoaded = QtWidgets.QWidget()
self.page_quickSessionLoaded.setObjectName("page_quickSessionLoaded")
self.verticalLayout_8 = QtWidgets.QVBoxLayout(self.page_quickSessionLoaded)
self.verticalLayout_8.setContentsMargins(0, 0, 0, 0)
self.verticalLayout_8.setSpacing(0)
self.verticalLayout_8.setObjectName("verticalLayout_8")
self.quickSessionNameLineEdit = QtWidgets.QLineEdit(self.page_quickSessionLoaded)
self.quickSessionNameLineEdit.setAlignment(QtCore.Qt.AlignCenter)
self.quickSessionNameLineEdit.setObjectName("quickSessionNameLineEdit")
self.verticalLayout_8.addWidget(self.quickSessionNameLineEdit)
self.widget_2 = QtWidgets.QWidget(self.page_quickSessionLoaded)
self.widget_2.setObjectName("widget_2")
self.horizontalLayout_4 = QtWidgets.QHBoxLayout(self.widget_2)
self.horizontalLayout_4.setContentsMargins(0, -1, 0, -1)
self.horizontalLayout_4.setObjectName("horizontalLayout_4")
self.quickSaveOpenSession = QtWidgets.QPushButton(self.widget_2)
self.quickSaveOpenSession.setObjectName("quickSaveOpenSession")
self.horizontalLayout_4.addWidget(self.quickSaveOpenSession)
self.quickCloseOpenSession = QtWidgets.QPushButton(self.widget_2)
self.quickCloseOpenSession.setObjectName("quickCloseOpenSession")
self.horizontalLayout_4.addWidget(self.quickCloseOpenSession)
self.verticalLayout_8.addWidget(self.widget_2)
self.splitter = QtWidgets.QSplitter(self.page_quickSessionLoaded)
self.splitter.setOrientation(QtCore.Qt.Vertical)
self.splitter.setObjectName("splitter")
self.quickSessionNotesGroupBox = QtWidgets.QGroupBox(self.splitter)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(1)
sizePolicy.setHeightForWidth(self.quickSessionNotesGroupBox.sizePolicy().hasHeightForWidth())
self.quickSessionNotesGroupBox.setSizePolicy(sizePolicy)
self.quickSessionNotesGroupBox.setObjectName("quickSessionNotesGroupBox")
self.verticalLayout_10 = QtWidgets.QVBoxLayout(self.quickSessionNotesGroupBox)
self.verticalLayout_10.setContentsMargins(0, 0, 0, 0)
self.verticalLayout_10.setSpacing(0)
self.verticalLayout_10.setObjectName("verticalLayout_10")
self.quickSessionNotesPlainTextEdit = QtWidgets.QPlainTextEdit(self.quickSessionNotesGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.quickSessionNotesPlainTextEdit.sizePolicy().hasHeightForWidth())
self.quickSessionNotesPlainTextEdit.setSizePolicy(sizePolicy)
self.quickSessionNotesPlainTextEdit.setObjectName("quickSessionNotesPlainTextEdit")
self.verticalLayout_10.addWidget(self.quickSessionNotesPlainTextEdit)
self.quickSessionClientsListWidget = QtWidgets.QListWidget(self.splitter)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(6)
sizePolicy.setHeightForWidth(self.quickSessionClientsListWidget.sizePolicy().hasHeightForWidth())
self.quickSessionClientsListWidget.setSizePolicy(sizePolicy)
self.quickSessionClientsListWidget.setViewMode(QtWidgets.QListView.IconMode)
self.quickSessionClientsListWidget.setObjectName("quickSessionClientsListWidget")
self.verticalLayout_8.addWidget(self.splitter)
self.quickStackedWidget.addWidget(self.page_quickSessionLoaded)
self.horizontalLayout_2.addWidget(self.quickStackedWidget)
self.tabbyCat.addTab(self.tab_quick, "")
self.tab_detailed = QtWidgets.QWidget()
self.tab_detailed.setObjectName("tab_detailed")
self.horizontalLayout = QtWidgets.QHBoxLayout(self.tab_detailed)
@@ -143,8 +58,8 @@ class Ui_MainWindow(object):
self.checkBoxNested.setChecked(True)
self.checkBoxNested.setObjectName("checkBoxNested")
self.verticalLayout_3.addWidget(self.checkBoxNested)
spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout_3.addItem(spacerItem1)
spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout_3.addItem(spacerItem)
self.l_2.addWidget(self.widget)
self.session_tree = QtWidgets.QTreeWidget(self.stack_no_session)
self.session_tree.setObjectName("session_tree")
@@ -341,20 +256,12 @@ class Ui_MainWindow(object):
self.retranslateUi(MainWindow)
self.mainPageSwitcher.setCurrentIndex(0)
self.tabbyCat.setCurrentIndex(0)
self.quickStackedWidget.setCurrentIndex(0)
self.detailedStackedWidget.setCurrentIndex(1)
QtCore.QMetaObject.connectSlotsByName(MainWindow)

def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "Agordejo"))
self.quickNewSession.setText(_translate("MainWindow", "Start New Session"))
self.quickSessionNameLineEdit.setText(_translate("MainWindow", "Session Name Goes Here"))
self.quickSaveOpenSession.setText(_translate("MainWindow", "Save"))
self.quickCloseOpenSession.setText(_translate("MainWindow", "Save and Close"))
self.quickSessionNotesGroupBox.setTitle(_translate("MainWindow", "Session Notes"))
self.quickSessionClientsListWidget.setSortingEnabled(True)
self.tabbyCat.setTabText(self.tabbyCat.indexOf(self.tab_quick), _translate("MainWindow", "Quick View"))
self.button_new_session.setText(_translate("MainWindow", "New"))
self.button_load_selected_session.setText(_translate("MainWindow", "Load Selected"))
self.checkBoxNested.setText(_translate("MainWindow", "Tree View"))

+ 0
- 180
qtgui/designer/mainwindow.ui View File

@@ -45,186 +45,6 @@
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab_quick">
<attribute name="title">
<string>Quick View</string>
</attribute>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QStackedWidget" name="quickStackedWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="page_quickSessionChooser">
<layout class="QVBoxLayout" name="verticalLayout_9">
<item>
<widget class="QPushButton" name="quickNewSession">
<property name="text">
<string>Start New Session</string>
</property>
<property name="autoDefault">
<bool>true</bool>
</property>
<property name="default">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QScrollArea" name="scrollArea">
<property name="widgetResizable">
<bool>true</bool>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<widget class="QWidget" name="quickSessionChooser">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>893</width>
<height>607</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_7">
<item>
<spacer name="deleteMe">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>586</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_quickSessionLoaded">
<layout class="QVBoxLayout" name="verticalLayout_8">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLineEdit" name="quickSessionNameLineEdit">
<property name="text">
<string>Session Name Goes Here</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="widget_2" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<property name="leftMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<item>
<widget class="QPushButton" name="quickSaveOpenSession">
<property name="text">
<string>Save</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="quickCloseOpenSession">
<property name="text">
<string>Save and Close</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="QGroupBox" name="quickSessionNotesGroupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Session Notes</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_10">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QPlainTextEdit" name="quickSessionNotesPlainTextEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QListWidget" name="quickSessionClientsListWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>6</verstretch>
</sizepolicy>
</property>
<property name="viewMode">
<enum>QListView::IconMode</enum>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
</widget>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_detailed">
<attribute name="title">
<string>Full View</string>

+ 6
- 15
qtgui/mainwindow.py View File

@@ -43,8 +43,6 @@ from .helper import setPaletteAndFont
from .helper import iconFromString
from .sessiontreecontroller import SessionTreeController
from .opensessioncontroller import OpenSessionController
from .quicksessioncontroller import QuickSessionController
from .quickopensessioncontroller import QuickOpenSessionController
from .projectname import ProjectNameWidget
from .addclientprompt import askForExecutable, updateWordlist
from .waitdialog import WaitDialog
@@ -144,7 +142,7 @@ class MainWindow(QtWidgets.QMainWindow):
SettingsDialog.loadFromSettingsAndSendToEngine() #set blacklist, whitelist for programdatabase and addtional executable paths for environment

#TODO: Hide information tab until the feature is ready
self.ui.tabbyCat.removeTab(2)
self.ui.tabbyCat.removeTab(1)
self.programIcons = {} #executableName:QIcon. Filled by self._updateGUIWithCachedPrograms which calls _updateIcons
self.sessionController = SessionController(mainWindow=self)
self.systemTray = SystemTray(mainWindow=self)
@@ -248,7 +246,6 @@ class MainWindow(QtWidgets.QMainWindow):
updateWordlist() #addclientprompt.py
self._updateIcons()
self.sessionController.openSessionController.launcherTable.buildPrograms()
self.sessionController.quickOpenSessionController.buildCleanStarterClients(nsmSessionExportDict={}) #wants a dict parameter for callback compatibility, but doesn't use it

def _updateIcons(self):
logger.info("Creating icon database")
@@ -459,11 +456,9 @@ class MainWindow(QtWidgets.QMainWindow):
"""

class SessionController(object):
"""Controls the StackWidget that contains the Session Tree, Open Session/Client and their
quick and easy variants.
Can be controlled from up and down the hierarchy.
"""Controls the StackWidget that contains the Session Tree and Opened Session/Client.

While all tabs are open at the same time for simplicity we hide the menus when in quick-view.
Can be controlled from up and down the hierarchy.
"""

def __init__(self, mainWindow):
@@ -473,8 +468,6 @@ class SessionController(object):
self.sessionTreeController = SessionTreeController(mainWindow=mainWindow)
self.openSessionController = OpenSessionController(mainWindow=mainWindow)

self.quickSessionController = QuickSessionController(mainWindow=mainWindow)
self.quickOpenSessionController = QuickOpenSessionController(mainWindow=mainWindow)
self._connectMenu()

#Callbacks
@@ -491,12 +484,11 @@ class SessionController(object):

#GUI signals
self.mainWindow.ui.tabbyCat.currentChanged.connect(self._activeTabChanged)

self._activeTabChanged(self.mainWindow.ui.tabbyCat.currentIndex())
#self._activeTabChanged(self.mainWindow.ui.tabbyCat.currentIndex())

def _activeTabChanged(self, index:int):
"""index 0 quick, 1 detailed, 2 info"""
if index == 1: #detailed
"""index 0 is open session, 1 is info etc"""
if index == 0: #detailed
state = bool(api.currentSession())
else: #quick and information and future tabs
state = False
@@ -563,4 +555,3 @@ class SessionController(object):
raise ValueError(f"_switch accepts choose or open, not {page}")

self.mainWindow.ui.detailedStackedWidget.setCurrentIndex(pageIndex)
self.mainWindow.ui.quickStackedWidget.setCurrentIndex(pageIndex)

+ 0
- 332
qtgui/quickopensessioncontroller.py View File

@@ -1,332 +0,0 @@
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Copyright 2021, 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
from engine.config import METADATA #includes METADATA only. No other environmental setup is executed.

#QtGui
from .descriptiontextwidget import DescriptionController
from .resources import *


def nothing():
pass

class StarterClientItem(QtWidgets.QListWidgetItem):
""".desktop-like entry:
{'type': 'Application',
'name': 'Vico',
'genericname': 'Sequencer',
'comment':'Minimalistic midi sequencer with piano roll for JACK and NSM',
'exec': 'vico',
'icon': 'vico',
'terminal': 'false',
'startupnotify': 'false',
'categories': 'AudioVideo;Audio;X-Recorders;X-Multitrack;X-Jack;',
'x-nsm-capable': 'true', 'version': '1.0.1',
'agordejoFullPath': '/usr/bin/vico',
'agordejoExec': 'vico',
'whitelist': True}

This is the icon that is starter and status-indicator at once.
QuickSession has only one icon per agordejoExec.

If at least one program is running as nsmClient in the session we switch ourselves on and
save the status as self.nsmClientDict

We do not react to name overrides by nsm-data, nor do we react to labels or name changes
through reportedNames.
"""
allItems = {} #agordejoExec:StarterClientItem

def __init__(self, parentController, desktopEntry:dict):
self.parentController = parentController
self.desktopEntry = desktopEntry
self.agordejoExec = desktopEntry["agordejoExec"]
super().__init__(desktopEntry["name"], type=1000) #type 0 is default qt type. 1000 is subclassed user type)

self.nsmClientDict = None #aka nsmStatusDict if this exists it means at least one instance of this application is running in the session

if "comment" in desktopEntry:
self.setToolTip(desktopEntry["comment"])
else:
self.setToolTip(desktopEntry["name"])
programIcons = self.parentController.mainWindow.programIcons
assert programIcons
assert "agordejoExec" in desktopEntry, desktopEntry
if desktopEntry["agordejoExec"] in programIcons:
icon = programIcons[desktopEntry["agordejoExec"]]
self.setIcon(icon)
self.updateStatus(None) #removed/off


def updateStatus(self, clientDict:dict):
"""
api callback

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
"executable":None, #For dumb clients this is the same as reportedName.
"label":None, #str
"lastStatus":None, #str
"statusHistory":[], #list
"hasOptionalGUI": False, #bool
"visible": None, # bool
"dirty": None, # bool
}
"""
self.nsmClientDict = clientDict #for comparison with later status changes. Especially for stopped clients.
if clientDict is None:
self.removed()
else:
getattr(self, clientDict["lastStatus"], nothing)()

def _setIconOverlay(self, status:str):
options = {
"removed": ":alert.svg",
"stopped": ":power.svg",
"hidden": ":hidden.svg",
"ready": ":running.svg",
}

if status in options:
overlayPixmap = QtGui.QIcon(options[status]).pixmap(QtCore.QSize(30,30))
shadow = QtGui.QIcon(options[status]).pixmap(QtCore.QSize(35,35)) #original color is black

#Colorize overlay symbol. Painter works inplace.
painter = QtGui.QPainter(overlayPixmap);
painter.setCompositionMode(QtGui.QPainter.CompositionMode_SourceIn)
painter.fillRect(overlayPixmap.rect(), QtGui.QColor("cyan"))
painter.end()

icon = self.parentController.mainWindow.programIcons[self.agordejoExec]
pixmap = icon.pixmap(QtCore.QSize(70,70))

p = QtGui.QPainter(pixmap)

p.drawPixmap(0, -1, shadow)
p.drawPixmap(2, 2, overlayPixmap) #top left corner of icon, with some padding for the shadow
p.end()
ico = QtGui.QIcon(pixmap)
self.setIcon(ico)
else:
if self.agordejoExec in self.parentController.mainWindow.programIcons: #there was a strange bug once where this happened exactly one, and then everything was fine, including this icon. Some DB backwards compatibility.
ico = self.parentController.mainWindow.programIcons[self.agordejoExec]
self.setIcon(ico)


#Status
def ready(self):
if self.nsmClientDict["hasOptionalGUI"]:
if self.nsmClientDict["visible"]:
self._setIconOverlay("ready")
else:
self._setIconOverlay("hidden")
else:
self._setIconOverlay("ready")

#self.setFlags(QtCore.Qt.ItemIsEnabled)

def removed(self):
#self.setFlags(QtCore.Qt.NoItemFlags) #Black and white. We can still mouseClick through parent signal when set to NoItemFlags
self.nsmClientDict = None #in opposite to stop

def stopped(self):
self.setFlags(QtCore.Qt.ItemIsEnabled)
self._setIconOverlay("stopped")

def handleClick(self):
alreadyInSession = api.executableInSession(self.agordejoExec)
#Paranoia Start
if self.nsmClientDict is None and alreadyInSession:
#Caught double-click. do nothing, this is a user-accident
return
elif self.nsmClientDict:
assert alreadyInSession
elif alreadyInSession:
assert self.nsmClientDict
#Paranoia End

if not alreadyInSession:
api.clientAdd(self.agordejoExec) #triggers status update callback which activates our item.
elif self.nsmClientDict["lastStatus"] == "stopped":
api.clientResume(self.nsmClientDict["clientId"])
else:
api.clientToggleVisible(self.nsmClientDict["clientId"]) #api is tolerant to sending this to non-optional-GUI clients


class QuickOpenSessionController(object):
"""Controls the widget, but does not subclass.

We want the simplest form of interaction possible: single touch.
No selections, no right click. Like a smartphone app.
"""

def __init__(self, mainWindow):
iconSize = 70
self.mainWindow = mainWindow
self.listWidget = mainWindow.ui.quickSessionClientsListWidget
self.listWidget.setIconSize(QtCore.QSize(iconSize,iconSize))
self.listWidget.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
self.listWidget.setHorizontalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
self.listWidget.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection) #Icons can't be selected. Text still can
self.listWidget.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
self.listWidget.setResizeMode(QtWidgets.QListView.Adjust)
self.listWidget.setGridSize(QtCore.QSize(iconSize*1.2,iconSize*2)) #x spacing, y for text
self.listWidget.setWordWrap(True) #needed for grid, don't use without grid (i.e. setSparcing and setUniformItemSizes)
#self.listWidget.setSpacing(20) # Grid is better
#self.listWidget.setUniformItemSizes(True) # Grid is better


self._nsmSessionExportDict = None
self.nameWidget = mainWindow.ui.quickSessionNameLineEdit
self.layout = mainWindow.ui.page_quickSessionLoaded.layout()
self.clientOverrideNamesCache = None #None or dict. Dict is never truly empty, it has at least empty categories.
self.descriptionController = DescriptionController(mainWindow, self.mainWindow.ui.quickSessionNotesGroupBox, self.mainWindow.ui.quickSessionNotesPlainTextEdit)

font = self.nameWidget.font()
font.setPixelSize(font.pixelSize() * 1.4)
self.nameWidget.setFont(font)

#GUI Signals
#self.listWidget.itemActivated.connect(lambda item:print (item)) #Activated is system dependend. On osx it might be single clicke, here on linux is double click. on phones it might be something else.
self.listWidget.itemClicked.connect(self._itemClicked)

mainWindow.ui.quickCloseOpenSession.clicked.connect(api.sessionClose)
font = mainWindow.ui.quickCloseOpenSession.font()
font.setPixelSize(font.pixelSize() * 1.2)
mainWindow.ui.quickCloseOpenSession.setFont(font)

mainWindow.ui.quickSaveOpenSession.clicked.connect(api.sessionSave)
mainWindow.ui.quickSaveOpenSession.hide()

#API Callbacks
api.callbacks.sessionOpenLoading.append(self.buildCleanStarterClients)
api.callbacks.sessionOpenLoading.append(self._openLoading)
api.callbacks.sessionOpenReady.append(self._openReady)
api.callbacks.sessionClosed.append(self._sendNameChange)
api.callbacks.clientStatusChanged.append(self._clientStatusChanged)

self.listWidget.setFocus() #take focus away from title-edit

logger.info("Quick Open Session Controller ready")

def _itemClicked(self, item):
self.listWidget.reset() #Hackity Hack! This is intended to revert the text-selection of items. However, that is not a real selection. clearSelection does nothing! Now it looks like a brief flash.
item.handleClick()



def _openLoading(self, nsmSessionExportDict):
self._nsmSessionExportDict = nsmSessionExportDict
self.nameWidget.setText(nsmSessionExportDict["nsmSessionName"])

def _openReady(self, nsmSessionExportDict):
self._nsmSessionExportDict = nsmSessionExportDict
self.nameWidget.setText(nsmSessionExportDict["nsmSessionName"])

def _sendNameChange(self):
"""The closed callback is send on start to indicate "no open session". exportDict cache is
not ready then. We need to test.
It is not possible to rename a running session. We allow the user to fake-edit the name
but will only send the api request after the session is closed"""
if not self._nsmSessionExportDict: #see docstring
return

if self.nameWidget.text() and not self.nameWidget.text() == self._nsmSessionExportDict["nsmSessionName"]:
logger.info(f"Instructing the api to rename session {self._nsmSessionExportDict['nsmSessionName']} to {self.nameWidget.text()} on close")
api.sessionRename(self._nsmSessionExportDict["nsmSessionName"], self.nameWidget.text())

self._nsmSessionExportDict = None #now really closed

def buildCleanStarterClients(self, nsmSessionExportDict:dict):
"""Reset everything to the initial, empty state.
We do not reset in openReady because that signifies that the session is ready.
And not in session closed because we want to setup data structures.

In comparison with the detailed view open session controller we need to do incremental
updates. The detailed view can just delete and recreater its launchers after a DB-update,
but we combine both views. So we can't just delete-and-rebuild because that destroys
running client states.
"""
engineCache = api.getCache()
programs = engineCache["programs"]

whitelist = [e for e in programs if e["whitelist"]]
leftovers = set(StarterClientItem.allItems.keys()) #"agordejoExec"

notForQuickView = ("nsm-data", "jackpatch", "nsm-proxy", "non-midi-mapper", "non-mixer-noui", "ray-proxy", "ray-jackpatch", "carla-jack-single", "carla-jack-multi")

for forIcon in StarterClientItem.allItems.values():
forIcon._setIconOverlay("") #empty initial state

for entry in whitelist:
exe = entry["agordejoExec"]
if exe in StarterClientItem.allItems:
if entry["agordejoExec"] in leftovers: #It happened that it was not. Don't ask me...
leftovers.remove(entry["agordejoExec"])
else:
#Create new. Item will be parented by Qt, so Python GC will not delete
if not exe in notForQuickView:
item = StarterClientItem(parentController=self, desktopEntry=entry)
self.listWidget.addItem(item)
StarterClientItem.allItems[entry["agordejoExec"]] = item

#Remove starters that were available until they got removed in the last db update
for loexe in leftovers:
item = StarterClientItem.allItems[loexe]
del StarterClientItem.allItems[loexe]
index = self.listWidget.indexFromItem(item).row() #Row is the real index in a listView, no matter iconViewMode.
self.listWidget.takeItem(index)
del item


def _clientStatusChanged(self, clientDict:dict):
"""Maps to nsmd status changes.
We already have icons for all programs, in opposite to detailed-view opensession controller.
Status updates are used to switch them on an off.
We also present only one icon per executable. If you want more go into the other mode.
"""
#index = self.listWidget.indexFromItem(QuickClientItem.allItems[clientId]).row() #Row is the real index in a listView, no matter iconViewMode.

assert clientDict["executable"]

if clientDict["dumbClient"]: #only real nsm clients in our session, whic includes the initial "not-yet" status of nsm-clients.
return
backgroundClients = METADATA["preferredClients"].values()
if clientDict["executable"] in backgroundClients:
return

if clientDict["executable"] in StarterClientItem.allItems:
item = StarterClientItem.allItems[clientDict["executable"]]
item.updateStatus(clientDict)
else:
logging.warning(f"Got client status update for {clientDict['executable']}, which is not in our database. This can happen if you install a program and do not update the DB. Please do so and then restart the session.")

+ 0
- 127
qtgui/quicksessioncontroller.py View File

@@ -1,127 +0,0 @@
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Copyright 2021, 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 datetime

#Third Party
from PyQt5 import QtCore, QtGui, QtWidgets

#Engine
from engine.config import METADATA #includes METADATA only. No other environmental setup is executed.
import engine.api as api



class SessionButton(QtWidgets.QPushButton):

def __init__(self, sessionDict):
self.sessionDict = sessionDict
super().__init__(sessionDict["nsmSessionName"])
self.clicked.connect(self.openSession)
#self.setFlat(True)

font = self.font()
font.setPixelSize(font.pixelSize() * 1.2 )
self.setFont(font)

#width = self.fontMetrics().boundingRect(sessionDict["nsmSessionName"]).width()+10
#width = self.fontMetrics().boundingRect(longestSessionName).width()+10
#width = parent.geometry().width()
#self.setFixedSize(width, 40)
self.setFixedHeight(40)


def openSession(self):
name = self.sessionDict["nsmSessionName"]
api.sessionOpen(name)

class QuickSessionController(object):
"""Controls the widget, but does not subclass"""

def __init__(self, mainWindow):
self.mainWindow = mainWindow
self.layout = mainWindow.ui.quickSessionChooser.layout()
#self.layout.setAlignment(QtCore.Qt.AlignHCenter)
newSessionButton = mainWindow.ui.quickNewSession

font = newSessionButton.font()
font.setPixelSize(font.pixelSize() * 1.4)
newSessionButton.setFont(font)

newSessionButton.setFixedHeight(40)
newSessionButton.setFocus(True) #Enter on program start creates a new session.
newSessionButton.clicked.connect(self._newTimestampSession)
api.callbacks.sessionsChanged.append(self._reactCallback_sessionsChanged)

#self.layout.geometry().width() #very small
#self.mainWindow.ui.quickSessionChooser.geometry().width() #too small
#self.mainWindow.ui.scrollArea.geometry().width()
#mainWindow.geometry().width()


logger.info("Quick Session Chooser ready")

def _clear(self):
"""Clear everything but the spacer item"""
for child in self.mainWindow.ui.quickSessionChooser.children():
if type(child) is SessionButton:
self.layout.removeWidget(child)
child.setParent(None)
del child



def _reactCallback_sessionsChanged(self, sessionDicts:list):
"""Main callback for new, added, removed, moved sessions etc."""
logger.info("Rebuilding session buttons")
self._clear() #except the space

spacer = self.layout.takeAt(0)

#longestSessionName = ""
#for sessionDict in sessionDicts:
# if len(sessionDict["nsmSessionName"]) > len(longestSessionName):
# longestSessionName = sessionDict["nsmSessionName"]

for sessionDict in sorted(sessionDicts, key=lambda d: d["nsmSessionName"]):
self.layout.addWidget(SessionButton(sessionDict))

#Finally add vertical spacer
#spacerItem = QtWidgets.QSpacerItem(1, 1, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) #int w, int h, QSizePolicy::Policy hPolicy = QSizePolicy::Minimum, QSizePolicy::Policy vPolicy = QSizePolicy::Minimum
self.layout.addItem(spacer)

def _newTimestampSession(self):
nsmExecutables = api.getNsmExecutables() #type set, cached, very fast.
con = METADATA["preferredClients"]["data"]
data = METADATA["preferredClients"]["connections"]
startclients = []
if con in nsmExecutables:
startclients.append(con)
if data in nsmExecutables:
startclients.append(data)

#now = datetime.datetime.now().replace(second=0, microsecond=0).isoformat()[:-3]
now = datetime.datetime.now().replace(microsecond=0).isoformat()
name = now
api.sessionNew(name, startclients)

Loading…
Cancel
Save