mirror of
https://github.com/m-labs/artiq.git
synced 2025-01-10 02:53:35 +08:00
bdaaf3c1d7
It was possible to crash the dashboard by opening the context menu before an applet entry had been selected for the first time (e.g. immediately after startup) and selecting one of the Group CCB actions, as the enable update slot would not have been run.
274 lines
11 KiB
Python
274 lines
11 KiB
Python
import asyncio
|
|
import logging
|
|
|
|
from PyQt5 import QtCore, QtWidgets
|
|
|
|
from artiq.gui import applets
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class AppletsCCBDock(applets.AppletsDock):
|
|
def __init__(self, *args, **kwargs):
|
|
applets.AppletsDock.__init__(self, *args, **kwargs)
|
|
|
|
sep = QtWidgets.QAction(self.table)
|
|
sep.setSeparator(True)
|
|
self.table.addAction(sep)
|
|
|
|
ccbp_group_menu = QtWidgets.QMenu()
|
|
actiongroup = QtWidgets.QActionGroup(self.table)
|
|
actiongroup.setExclusive(True)
|
|
self.ccbp_group_none = QtWidgets.QAction("No policy", self.table)
|
|
self.ccbp_group_none.setCheckable(True)
|
|
self.ccbp_group_none.triggered.connect(lambda: self.set_ccbp(""))
|
|
ccbp_group_menu.addAction(self.ccbp_group_none)
|
|
actiongroup.addAction(self.ccbp_group_none)
|
|
self.ccbp_group_ignore = QtWidgets.QAction("Ignore requests", self.table)
|
|
self.ccbp_group_ignore.setCheckable(True)
|
|
self.ccbp_group_ignore.triggered.connect(lambda: self.set_ccbp("ignore"))
|
|
ccbp_group_menu.addAction(self.ccbp_group_ignore)
|
|
actiongroup.addAction(self.ccbp_group_ignore)
|
|
self.ccbp_group_create = QtWidgets.QAction("Create applets", self.table)
|
|
self.ccbp_group_create.setCheckable(True)
|
|
self.ccbp_group_create.triggered.connect(lambda: self.set_ccbp("create"))
|
|
ccbp_group_menu.addAction(self.ccbp_group_create)
|
|
actiongroup.addAction(self.ccbp_group_create)
|
|
self.ccbp_group_enable = QtWidgets.QAction("Create and enable/disable applets",
|
|
self.table)
|
|
self.ccbp_group_enable.setCheckable(True)
|
|
self.ccbp_group_enable.triggered.connect(lambda: self.set_ccbp("enable"))
|
|
ccbp_group_menu.addAction(self.ccbp_group_enable)
|
|
actiongroup.addAction(self.ccbp_group_enable)
|
|
self.ccbp_group_action = QtWidgets.QAction("Group CCB policy", self.table)
|
|
self.ccbp_group_action.setMenu(ccbp_group_menu)
|
|
self.table.addAction(self.ccbp_group_action)
|
|
self.table.itemSelectionChanged.connect(self.update_group_ccbp_menu)
|
|
self.update_group_ccbp_menu()
|
|
|
|
ccbp_global_menu = QtWidgets.QMenu()
|
|
actiongroup = QtWidgets.QActionGroup(self.table)
|
|
actiongroup.setExclusive(True)
|
|
self.ccbp_global_ignore = QtWidgets.QAction("Ignore requests", self.table)
|
|
self.ccbp_global_ignore.setCheckable(True)
|
|
ccbp_global_menu.addAction(self.ccbp_global_ignore)
|
|
actiongroup.addAction(self.ccbp_global_ignore)
|
|
self.ccbp_global_create = QtWidgets.QAction("Create applets", self.table)
|
|
self.ccbp_global_create.setCheckable(True)
|
|
self.ccbp_global_create.setChecked(True)
|
|
ccbp_global_menu.addAction(self.ccbp_global_create)
|
|
actiongroup.addAction(self.ccbp_global_create)
|
|
self.ccbp_global_enable = QtWidgets.QAction("Create and enable/disable applets",
|
|
self.table)
|
|
self.ccbp_global_enable.setCheckable(True)
|
|
ccbp_global_menu.addAction(self.ccbp_global_enable)
|
|
actiongroup.addAction(self.ccbp_global_enable)
|
|
ccbp_global_action = QtWidgets.QAction("Global CCB policy", self.table)
|
|
ccbp_global_action.setMenu(ccbp_global_menu)
|
|
self.table.addAction(ccbp_global_action)
|
|
|
|
def update_group_ccbp_menu(self):
|
|
selection = self.table.selectedItems()
|
|
if selection:
|
|
item = selection[0]
|
|
if item.ty == "applet":
|
|
item = item.parent()
|
|
if item is None:
|
|
self.ccbp_group_action.setEnabled(False)
|
|
else:
|
|
self.ccbp_group_action.setEnabled(True)
|
|
ccbp = item.text(1)
|
|
if ccbp == "":
|
|
self.ccbp_group_none.setChecked(True)
|
|
else:
|
|
getattr(self, "ccbp_group_" + ccbp).setChecked(True)
|
|
else:
|
|
self.ccbp_group_action.setEnabled(False)
|
|
|
|
def set_ccbp(self, ccbp):
|
|
item = self.table.selectedItems()[0]
|
|
if item.ty == "applet":
|
|
item = item.parent()
|
|
item.setText(1, ccbp)
|
|
|
|
def get_ccpb_global(self):
|
|
if self.ccbp_global_ignore.isChecked():
|
|
return "ignore"
|
|
if self.ccbp_global_create.isChecked():
|
|
return "create"
|
|
if self.ccbp_global_enable.isChecked():
|
|
return "enable"
|
|
|
|
def get_ccpb(self, group):
|
|
ccbp = self.get_ccpb_global()
|
|
parent = self.table.invisibleRootItem()
|
|
for g in group:
|
|
new_parent = None
|
|
for i in range(parent.childCount()):
|
|
child = parent.child(i)
|
|
if child.ty == "group" and child.text(0) == g:
|
|
c_ccbp = child.text(1)
|
|
if c_ccbp:
|
|
ccbp = c_ccbp
|
|
new_parent = child
|
|
break
|
|
if new_parent is None:
|
|
return ccbp
|
|
else:
|
|
parent = new_parent
|
|
return ccbp
|
|
|
|
def locate_applet(self, name, group, create_groups):
|
|
parent = self.table.invisibleRootItem()
|
|
for g in group:
|
|
new_parent = None
|
|
for i in range(parent.childCount()):
|
|
child = parent.child(i)
|
|
if child.ty == "group" and child.text(0) == g:
|
|
new_parent = child
|
|
break
|
|
if new_parent is None:
|
|
if create_groups:
|
|
new_parent = self.new_group(g, parent=parent)
|
|
else:
|
|
return None, None
|
|
parent = new_parent
|
|
|
|
applet = None
|
|
for i in range(parent.childCount()):
|
|
child = parent.child(i)
|
|
if child.ty == "applet" and child.text(0) == name:
|
|
applet = child
|
|
break
|
|
return parent, applet
|
|
|
|
def ccb_create_applet(self, name, command, group=None, code=None):
|
|
"""Requests the creation of a new applet.
|
|
|
|
An applet is identified by its name and an optional list of groups that
|
|
represent a path (nested groups). If ``group`` is a string, it
|
|
corresponds to a single group. If ``group`` is ``None`` or an empty
|
|
list, it corresponds to the root.
|
|
|
|
``command`` gives the command line used to run the applet, as if it was
|
|
started from a shell. The dashboard substitutes variables such as
|
|
``$python`` that gives the complete file name of the Python interpreter
|
|
running the dashboard.
|
|
|
|
If the name already exists (after following any specified groups), the
|
|
command or code of the existing applet with that name is replaced, and
|
|
the applet is restarted and shown at its previous position. If not, a
|
|
new applet entry is created and the applet is shown at any position on
|
|
the screen.
|
|
|
|
If the group(s) do not exist, they are created.
|
|
|
|
If ``code`` is not ``None``, it should be a string that contains the
|
|
full source code of the applet. In this case, ``command`` is used to
|
|
specify (optional) command-line arguments to the applet.
|
|
|
|
This function is called when a CCB ``create_applet`` is issued.
|
|
"""
|
|
if group is None:
|
|
group = []
|
|
elif isinstance(group, str):
|
|
group = [group]
|
|
|
|
ccbp = self.get_ccpb(group)
|
|
if ccbp == "ignore":
|
|
return
|
|
parent, applet = self.locate_applet(name, group, True)
|
|
if code is None:
|
|
spec = {"ty": "command", "command": command}
|
|
else:
|
|
spec = {"ty": "code", "code": code, "command": command}
|
|
if applet is None:
|
|
logger.debug("Applet %s does not exist: creating", name)
|
|
applet = self.new(name=name, spec=spec, parent=parent)
|
|
else:
|
|
if spec != self.get_spec(applet):
|
|
logger.debug("Applet %s already exists: updating existing spec", name)
|
|
self.set_spec(applet, spec)
|
|
if applet.applet_dock:
|
|
asyncio.ensure_future(applet.applet_dock.restart())
|
|
else:
|
|
logger.debug("Applet %s already exists and no update required", name)
|
|
|
|
if ccbp == "enable":
|
|
applet.setCheckState(0, QtCore.Qt.Checked)
|
|
|
|
def ccb_disable_applet(self, name, group=None):
|
|
"""Disables an applet.
|
|
|
|
The applet is identified by its name, after following any specified
|
|
groups.
|
|
|
|
This function is called when a CCB ``disable_applet`` is issued.
|
|
"""
|
|
if group is None:
|
|
group = []
|
|
elif isinstance(group, str):
|
|
group = [group]
|
|
|
|
ccbp = self.get_ccpb(group)
|
|
if ccbp != "enable":
|
|
return
|
|
parent, applet = self.locate_applet(name, group, False)
|
|
if applet is not None:
|
|
applet.setCheckState(0, QtCore.Qt.Unchecked)
|
|
|
|
def ccb_disable_applet_group(self, group):
|
|
"""Disables all the applets in a group.
|
|
|
|
If the group is nested, ``group`` should be a list, with the names
|
|
of the parents preceding the name of the group to disable.
|
|
|
|
This function is called when a CCB ``disable_applet_group`` is issued.
|
|
"""
|
|
if isinstance(group, str):
|
|
group = [group]
|
|
|
|
ccbp = self.get_ccpb(group)
|
|
if ccbp != "enable":
|
|
return
|
|
if not group:
|
|
return
|
|
wi = self.table.invisibleRootItem()
|
|
for g in group:
|
|
nwi = None
|
|
for i in range(wi.childCount()):
|
|
child = wi.child(i)
|
|
if child.ty == "group" and child.text(0) == g:
|
|
nwi = child
|
|
break
|
|
if nwi is None:
|
|
return
|
|
else:
|
|
wi = nwi
|
|
wi.setCheckState(0, QtCore.Qt.Unchecked)
|
|
|
|
def ccb_notify(self, message):
|
|
try:
|
|
service = message["service"]
|
|
args = message["args"]
|
|
kwargs = message["kwargs"]
|
|
if service == "create_applet":
|
|
self.ccb_create_applet(*args, **kwargs)
|
|
elif service == "disable_applet":
|
|
self.ccb_disable_applet(*args, **kwargs)
|
|
elif service == "disable_applet_group":
|
|
self.ccb_disable_applet_group(*args, **kwargs)
|
|
except:
|
|
logger.error("failed to process CCB", exc_info=True)
|
|
|
|
def save_state(self):
|
|
return {
|
|
"applets": applets.AppletsDock.save_state(self),
|
|
"ccbp_global": self.get_ccpb_global()
|
|
}
|
|
|
|
def restore_state(self, state):
|
|
applets.AppletsDock.restore_state(self, state["applets"])
|
|
getattr(self, "ccbp_global_" + state["ccbp_global"]).setChecked(True)
|