forked from M-Labs/artiq
1
0
Fork 0

gui/applets: support groups, creating and deleting applet groups, renaming groups, moving applets from one group to another and reordering applets and groups via drag-and-drop

This commit is contained in:
Sebastien Bourdeauducq 2016-09-04 23:09:26 +08:00
parent 9fd9235e44
commit 8f6c4451ac
1 changed files with 212 additions and 107 deletions

View File

@ -305,20 +305,25 @@ class AppletsDock(QtWidgets.QDockWidget):
self.main_window = main_window self.main_window = main_window
self.datasets_sub = datasets_sub self.datasets_sub = datasets_sub
self.dock_to_checkbox = dict() self.dock_to_item = dict()
self.applet_uids = set() self.applet_uids = set()
self.table = QtWidgets.QTableWidget(0, 3) self.table = QtWidgets.QTreeWidget()
self.table.setHorizontalHeaderLabels(["Enable", "Name", "Command"]) self.table.setColumnCount(3)
self.table.setHeaderLabels(["Enable", "Name", "Command"])
self.table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) self.table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
self.table.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection) self.table.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
self.table.horizontalHeader().setStretchLastSection(True)
self.table.horizontalHeader().setSectionResizeMode( self.table.header().setStretchLastSection(True)
self.table.header().setSectionResizeMode(
QtWidgets.QHeaderView.ResizeToContents) QtWidgets.QHeaderView.ResizeToContents)
self.table.verticalHeader().setSectionResizeMode(
QtWidgets.QHeaderView.ResizeToContents)
self.table.verticalHeader().hide()
self.table.setTextElideMode(QtCore.Qt.ElideNone) self.table.setTextElideMode(QtCore.Qt.ElideNone)
self.table.setDragEnabled(True)
self.table.viewport().setAcceptDrops(True)
self.table.setDropIndicatorShown(True)
self.table.setDragDropMode(QtWidgets.QAbstractItemView.InternalMove)
self.setWidget(self.table) self.setWidget(self.table)
completer_delegate = _CompleterDelegate() completer_delegate = _CompleterDelegate()
@ -327,28 +332,32 @@ class AppletsDock(QtWidgets.QDockWidget):
self.table.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu) self.table.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
new_action = QtWidgets.QAction("New applet", self.table) new_action = QtWidgets.QAction("New applet", self.table)
new_action.triggered.connect(lambda: self.new()) new_action.triggered.connect(partial(self.new_with_parent, self.new))
self.table.addAction(new_action) self.table.addAction(new_action)
templates_menu = QtWidgets.QMenu() templates_menu = QtWidgets.QMenu()
for name, template in _templates: for name, template in _templates:
action = QtWidgets.QAction(name, self.table) action = QtWidgets.QAction(name, self.table)
action.triggered.connect(partial(self.new_template, template)) action.triggered.connect(partial(
self.new_with_parent, self.new, command=template))
templates_menu.addAction(action) templates_menu.addAction(action)
restart_action = QtWidgets.QAction("New applet from template", self.table) restart_action = QtWidgets.QAction("New applet from template", self.table)
restart_action.setMenu(templates_menu) restart_action.setMenu(templates_menu)
self.table.addAction(restart_action) self.table.addAction(restart_action)
restart_action = QtWidgets.QAction("Restart selected applet", self.table) restart_action = QtWidgets.QAction("Restart selected applet or group", self.table)
restart_action.setShortcut("CTRL+R") restart_action.setShortcut("CTRL+R")
restart_action.setShortcutContext(QtCore.Qt.WidgetShortcut) restart_action.setShortcutContext(QtCore.Qt.WidgetShortcut)
restart_action.triggered.connect(self.restart) restart_action.triggered.connect(self.restart)
self.table.addAction(restart_action) self.table.addAction(restart_action)
delete_action = QtWidgets.QAction("Delete selected applet", self.table) delete_action = QtWidgets.QAction("Delete selected applet or group", self.table)
delete_action.setShortcut("DELETE") delete_action.setShortcut("DELETE")
delete_action.setShortcutContext(QtCore.Qt.WidgetShortcut) delete_action.setShortcutContext(QtCore.Qt.WidgetShortcut)
delete_action.triggered.connect(self.delete) delete_action.triggered.connect(self.delete)
self.table.addAction(delete_action) self.table.addAction(delete_action)
new_group_action = QtWidgets.QAction("New group", self.table)
new_group_action.triggered.connect(partial(self.new_with_parent, self.new_group))
self.table.addAction(new_group_action)
self.table.cellChanged.connect(self.cell_changed) self.table.itemChanged.connect(self.item_changed)
def create(self, uid, name, command): def create(self, uid, name, command):
dock = _AppletDock(self.datasets_sub, uid, name, command) dock = _AppletDock(self.datasets_sub, uid, name, command)
@ -358,122 +367,218 @@ class AppletsDock(QtWidgets.QDockWidget):
dock.sigClosed.connect(partial(self.on_dock_closed, dock)) dock.sigClosed.connect(partial(self.on_dock_closed, dock))
return dock return dock
def cell_changed(self, row, column): def item_changed(self, item, column):
if column == 0: if item.ty == "applet":
item = self.table.item(row, column) if column == 0:
if item.checkState() == QtCore.Qt.Checked: if item.checkState(0) == QtCore.Qt.Checked:
command = self.table.item(row, 2) command = item.text(2)
if command: if command:
command = command.text() name = item.text(1)
name = self.table.item(row, 1) dock = self.create(item.applet_uid, name, command)
if name is None: item.applet_dock = dock
name = "" if item.applet_geometry is not None:
else: dock.restoreGeometry(item.applet_geometry)
name = name.text() # geometry is now handled by main window state
dock = self.create(item.applet_uid, name, command) item.applet_geometry = None
item.applet_dock = dock self.dock_to_item[dock] = item
if item.applet_geometry is not None: else:
dock.restoreGeometry(item.applet_geometry) dock = item.applet_dock
# geometry is now handled by main window state if dock is not None:
item.applet_geometry = None # This calls self.on_dock_closed
self.dock_to_checkbox[dock] = item dock.close()
else: elif column == 1 or column == 2:
new_value = item.text(column)
dock = item.applet_dock dock = item.applet_dock
if dock is not None: if dock is not None:
# This calls self.on_dock_closed if column == 1:
dock.close() dock.rename(new_value)
elif column == 1 or column == 2: else:
new_value = self.table.item(row, column).text() dock.command = new_value
dock = self.table.item(row, 0).applet_dock elif item.ty == "group":
if dock is not None: # To Qt's credit, it already does everything for us here.
if column == 1: pass
dock.rename(new_value) else:
else: raise ValueError
dock.command = new_value
def on_dock_closed(self, dock): def on_dock_closed(self, dock):
checkbox_item = self.dock_to_checkbox[dock] item = self.dock_to_item[dock]
checkbox_item.applet_dock = None item.applet_dock = None
checkbox_item.applet_geometry = dock.saveGeometry() item.applet_geometry = dock.saveGeometry()
asyncio.ensure_future(dock.terminate()) asyncio.ensure_future(dock.terminate())
del self.dock_to_checkbox[dock] del self.dock_to_item[dock]
checkbox_item.setCheckState(QtCore.Qt.Unchecked) item.setCheckState(0, QtCore.Qt.Unchecked)
def new(self, uid=None): def get_untitled(self):
existing_names = set()
def walk(wi):
for i in range(wi.childCount()):
cwi = wi.child(i)
existing_names.add(cwi.text(1))
walk(cwi)
walk(self.table.invisibleRootItem())
i = 1
name = "untitled"
while name in existing_names:
i += 1
name = "untitled " + str(i)
return name
def new(self, uid=None, name=None, command="", parent=None):
if uid is None: if uid is None:
uid = next(i for i in count() if i not in self.applet_uids) uid = next(i for i in count() if i not in self.applet_uids)
assert uid not in self.applet_uids, uid assert uid not in self.applet_uids, uid
self.applet_uids.add(uid) self.applet_uids.add(uid)
row = self.table.rowCount() if name is None:
self.table.insertRow(row) name = self.get_untitled()
checkbox = QtWidgets.QTableWidgetItem() item = QtWidgets.QTreeWidgetItem(["", name, command])
checkbox.setFlags(QtCore.Qt.ItemIsSelectable | item.ty = "applet"
QtCore.Qt.ItemIsUserCheckable | item.setFlags(QtCore.Qt.ItemIsSelectable |
QtCore.Qt.ItemIsEnabled) QtCore.Qt.ItemIsUserCheckable |
checkbox.setCheckState(QtCore.Qt.Unchecked) QtCore.Qt.ItemIsEditable |
checkbox.applet_uid = uid QtCore.Qt.ItemIsDragEnabled |
checkbox.applet_dock = None QtCore.Qt.ItemNeverHasChildren |
checkbox.applet_geometry = None QtCore.Qt.ItemIsEnabled)
self.table.setItem(row, 0, checkbox) item.setCheckState(0, QtCore.Qt.Unchecked)
self.table.setItem(row, 1, QtWidgets.QTableWidgetItem()) item.applet_uid = uid
self.table.setItem(row, 2, QtWidgets.QTableWidgetItem()) item.applet_dock = None
return row item.applet_geometry = None
item.setIcon(0, QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_ComputerIcon))
if parent is None:
self.table.addTopLevelItem(item)
else:
parent.addChild(item)
return item
def new_template(self, template): def new_group(self, name=None, parent=None):
row = self.new() if name is None:
self.table.item(row, 2).setText(template) name = self.get_untitled()
item = QtWidgets.QTreeWidgetItem(["", name])
item.ty = "group"
item.setFlags(QtCore.Qt.ItemIsSelectable |
QtCore.Qt.ItemIsEditable |
QtCore.Qt.ItemIsUserCheckable |
QtCore.Qt.ItemIsAutoTristate |
QtCore.Qt.ItemIsDragEnabled |
QtCore.Qt.ItemIsDropEnabled |
QtCore.Qt.ItemIsEnabled)
item.setIcon(0, QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_DirIcon))
if parent is None:
self.table.addTopLevelItem(item)
else:
parent.addChild(item)
return item
def new_with_parent(self, cb, **kwargs):
parent = None
selection = self.table.selectedItems()
if selection:
parent = selection[0]
if parent.ty == "applet":
parent = parent.parent()
if parent is not None:
parent.setExpanded(True)
cb(parent=parent, **kwargs)
def restart(self): def restart(self):
selection = self.table.selectedRanges() selection = self.table.selectedItems()
if selection: if selection:
row = selection[0].topRow() item = selection[0]
dock = self.table.item(row, 0).applet_dock def walk(wi):
if dock is not None: if wi.ty == "applet":
asyncio.ensure_future(dock.restart()) dock = wi.applet_dock
if dock is not None:
asyncio.ensure_future(dock.restart())
elif wi.ty == "group":
for i in range(wi.childCount()):
walk(wi.child(i))
else:
raise ValueError
walk(item)
def delete(self): def delete(self):
selection = self.table.selectedRanges() selection = self.table.selectedItems()
if selection: if selection:
row = selection[0].topRow() item = selection[0]
item = self.table.item(row, 0)
dock = item.applet_dock def recursive_delete(wi):
if dock is not None: if wi.ty == "applet":
# This calls self.on_dock_closed dock = wi.applet_dock
dock.close() if dock is not None:
self.applet_uids.remove(item.applet_uid) # This calls self.on_dock_closed
self.table.removeRow(row) dock.close()
self.applet_uids.remove(wi.applet_uid)
elif wi.ty == "group":
for i in range(wi.childCount()):
recursive_delete(wi.child(i))
else:
raise ValueError
recursive_delete(item)
parent = item.parent()
if parent is None:
parent = self.table.invisibleRootItem()
parent.removeChild(item)
async def stop(self): async def stop(self):
for row in range(self.table.rowCount()): async def walk(wi):
dock = self.table.item(row, 0).applet_dock for row in range(wi.childCount()):
if dock is not None: cwi = wi.child(row)
await dock.terminate() if cwi.ty == "applet":
dock = cwi.applet_dock
if dock is not None:
await dock.terminate()
elif cwi.ty == "group":
await walk(cwi)
else:
raise ValueError
await walk(self.table.invisibleRootItem())
def save_state(self): def save_state_item(self, wi):
state = [] state = []
for row in range(self.table.rowCount()): for row in range(wi.childCount()):
uid = self.table.item(row, 0).applet_uid cwi = wi.child(row)
enabled = self.table.item(row, 0).checkState() == QtCore.Qt.Checked if cwi.ty == "applet":
name = self.table.item(row, 1).text() uid = cwi.applet_uid
command = self.table.item(row, 2).text() enabled = cwi.checkState(0) == QtCore.Qt.Checked
geometry = self.table.item(row, 0).applet_geometry name = cwi.text(1)
if geometry is not None: command = cwi.text(2)
geometry = bytes(geometry) geometry = cwi.applet_geometry
state.append((uid, enabled, name, command, geometry)) if geometry is not None:
geometry = bytes(geometry)
state.append(("applet", uid, enabled, name, command, geometry))
elif cwi.ty == "group":
name = cwi.text(1)
expanded = cwi.isExpanded()
state_child = self.save_state_item(cwi)
state.append(("group", name, expanded, state_child))
else:
raise ValueError
return state return state
def save_state(self):
return self.save_state_item(self.table.invisibleRootItem())
def restore_state_item(self, state, parent):
for wis in state:
if wis[0] == "applet":
_, uid, enabled, name, command, geometry = wis
item = self.new(uid, name, command, parent=parent)
if geometry is not None:
geometry = QtCore.QByteArray(geometry)
item.applet_geometry = geometry
if enabled:
item.setCheckState(0, QtCore.Qt.Checked)
elif wis[0] == "group":
_, name, expanded, state_child = wis
item = self.new_group(name, parent=parent)
item.setExpanded(expanded)
self.restore_state_item(state_child, item)
else:
raise ValueError("Invalid item state: " + str(wis[0]))
def restore_state(self, state): def restore_state(self, state):
for uid, enabled, name, command, geometry in state: self.restore_state_item(state, None)
row = self.new(uid)
item = QtWidgets.QTableWidgetItem()
item.setText(name)
self.table.setItem(row, 1, item)
item = QtWidgets.QTableWidgetItem()
item.setText(command)
self.table.setItem(row, 2, item)
if geometry is not None:
geometry = QtCore.QByteArray(geometry)
self.table.item(row, 0).applet_geometry = geometry
if enabled:
self.table.item(row, 0).setCheckState(QtCore.Qt.Checked)