Skip to content

Commit 8f6c445

Browse files
committedSep 4, 2016
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
1 parent 9fd9235 commit 8f6c445

File tree

1 file changed

+214
-109
lines changed

1 file changed

+214
-109
lines changed
 

‎artiq/gui/applets.py

+214-109
Original file line numberDiff line numberDiff line change
@@ -305,20 +305,25 @@ def __init__(self, main_window, datasets_sub):
305305

306306
self.main_window = main_window
307307
self.datasets_sub = datasets_sub
308-
self.dock_to_checkbox = dict()
308+
self.dock_to_item = dict()
309309
self.applet_uids = set()
310310

311-
self.table = QtWidgets.QTableWidget(0, 3)
312-
self.table.setHorizontalHeaderLabels(["Enable", "Name", "Command"])
311+
self.table = QtWidgets.QTreeWidget()
312+
self.table.setColumnCount(3)
313+
self.table.setHeaderLabels(["Enable", "Name", "Command"])
313314
self.table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
314315
self.table.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
315-
self.table.horizontalHeader().setStretchLastSection(True)
316-
self.table.horizontalHeader().setSectionResizeMode(
317-
QtWidgets.QHeaderView.ResizeToContents)
318-
self.table.verticalHeader().setSectionResizeMode(
316+
317+
self.table.header().setStretchLastSection(True)
318+
self.table.header().setSectionResizeMode(
319319
QtWidgets.QHeaderView.ResizeToContents)
320-
self.table.verticalHeader().hide()
321320
self.table.setTextElideMode(QtCore.Qt.ElideNone)
321+
322+
self.table.setDragEnabled(True)
323+
self.table.viewport().setAcceptDrops(True)
324+
self.table.setDropIndicatorShown(True)
325+
self.table.setDragDropMode(QtWidgets.QAbstractItemView.InternalMove)
326+
322327
self.setWidget(self.table)
323328

324329
completer_delegate = _CompleterDelegate()
@@ -327,28 +332,32 @@ def __init__(self, main_window, datasets_sub):
327332

328333
self.table.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
329334
new_action = QtWidgets.QAction("New applet", self.table)
330-
new_action.triggered.connect(lambda: self.new())
335+
new_action.triggered.connect(partial(self.new_with_parent, self.new))
331336
self.table.addAction(new_action)
332337
templates_menu = QtWidgets.QMenu()
333338
for name, template in _templates:
334339
action = QtWidgets.QAction(name, self.table)
335-
action.triggered.connect(partial(self.new_template, template))
340+
action.triggered.connect(partial(
341+
self.new_with_parent, self.new, command=template))
336342
templates_menu.addAction(action)
337343
restart_action = QtWidgets.QAction("New applet from template", self.table)
338344
restart_action.setMenu(templates_menu)
339345
self.table.addAction(restart_action)
340-
restart_action = QtWidgets.QAction("Restart selected applet", self.table)
346+
restart_action = QtWidgets.QAction("Restart selected applet or group", self.table)
341347
restart_action.setShortcut("CTRL+R")
342348
restart_action.setShortcutContext(QtCore.Qt.WidgetShortcut)
343349
restart_action.triggered.connect(self.restart)
344350
self.table.addAction(restart_action)
345-
delete_action = QtWidgets.QAction("Delete selected applet", self.table)
351+
delete_action = QtWidgets.QAction("Delete selected applet or group", self.table)
346352
delete_action.setShortcut("DELETE")
347353
delete_action.setShortcutContext(QtCore.Qt.WidgetShortcut)
348354
delete_action.triggered.connect(self.delete)
349355
self.table.addAction(delete_action)
356+
new_group_action = QtWidgets.QAction("New group", self.table)
357+
new_group_action.triggered.connect(partial(self.new_with_parent, self.new_group))
358+
self.table.addAction(new_group_action)
350359

351-
self.table.cellChanged.connect(self.cell_changed)
360+
self.table.itemChanged.connect(self.item_changed)
352361

353362
def create(self, uid, name, command):
354363
dock = _AppletDock(self.datasets_sub, uid, name, command)
@@ -358,122 +367,218 @@ def create(self, uid, name, command):
358367
dock.sigClosed.connect(partial(self.on_dock_closed, dock))
359368
return dock
360369

361-
def cell_changed(self, row, column):
362-
if column == 0:
363-
item = self.table.item(row, column)
364-
if item.checkState() == QtCore.Qt.Checked:
365-
command = self.table.item(row, 2)
366-
if command:
367-
command = command.text()
368-
name = self.table.item(row, 1)
369-
if name is None:
370-
name = ""
371-
else:
372-
name = name.text()
373-
dock = self.create(item.applet_uid, name, command)
374-
item.applet_dock = dock
375-
if item.applet_geometry is not None:
376-
dock.restoreGeometry(item.applet_geometry)
377-
# geometry is now handled by main window state
378-
item.applet_geometry = None
379-
self.dock_to_checkbox[dock] = item
380-
else:
370+
def item_changed(self, item, column):
371+
if item.ty == "applet":
372+
if column == 0:
373+
if item.checkState(0) == QtCore.Qt.Checked:
374+
command = item.text(2)
375+
if command:
376+
name = item.text(1)
377+
dock = self.create(item.applet_uid, name, command)
378+
item.applet_dock = dock
379+
if item.applet_geometry is not None:
380+
dock.restoreGeometry(item.applet_geometry)
381+
# geometry is now handled by main window state
382+
item.applet_geometry = None
383+
self.dock_to_item[dock] = item
384+
else:
385+
dock = item.applet_dock
386+
if dock is not None:
387+
# This calls self.on_dock_closed
388+
dock.close()
389+
elif column == 1 or column == 2:
390+
new_value = item.text(column)
381391
dock = item.applet_dock
382392
if dock is not None:
383-
# This calls self.on_dock_closed
384-
dock.close()
385-
elif column == 1 or column == 2:
386-
new_value = self.table.item(row, column).text()
387-
dock = self.table.item(row, 0).applet_dock
388-
if dock is not None:
389-
if column == 1:
390-
dock.rename(new_value)
391-
else:
392-
dock.command = new_value
393+
if column == 1:
394+
dock.rename(new_value)
395+
else:
396+
dock.command = new_value
397+
elif item.ty == "group":
398+
# To Qt's credit, it already does everything for us here.
399+
pass
400+
else:
401+
raise ValueError
393402

394403
def on_dock_closed(self, dock):
395-
checkbox_item = self.dock_to_checkbox[dock]
396-
checkbox_item.applet_dock = None
397-
checkbox_item.applet_geometry = dock.saveGeometry()
404+
item = self.dock_to_item[dock]
405+
item.applet_dock = None
406+
item.applet_geometry = dock.saveGeometry()
398407
asyncio.ensure_future(dock.terminate())
399-
del self.dock_to_checkbox[dock]
400-
checkbox_item.setCheckState(QtCore.Qt.Unchecked)
401-
402-
def new(self, uid=None):
408+
del self.dock_to_item[dock]
409+
item.setCheckState(0, QtCore.Qt.Unchecked)
410+
411+
def get_untitled(self):
412+
existing_names = set()
413+
def walk(wi):
414+
for i in range(wi.childCount()):
415+
cwi = wi.child(i)
416+
existing_names.add(cwi.text(1))
417+
walk(cwi)
418+
walk(self.table.invisibleRootItem())
419+
420+
i = 1
421+
name = "untitled"
422+
while name in existing_names:
423+
i += 1
424+
name = "untitled " + str(i)
425+
return name
426+
427+
def new(self, uid=None, name=None, command="", parent=None):
403428
if uid is None:
404429
uid = next(i for i in count() if i not in self.applet_uids)
405430
assert uid not in self.applet_uids, uid
406431
self.applet_uids.add(uid)
407432

408-
row = self.table.rowCount()
409-
self.table.insertRow(row)
410-
checkbox = QtWidgets.QTableWidgetItem()
411-
checkbox.setFlags(QtCore.Qt.ItemIsSelectable |
412-
QtCore.Qt.ItemIsUserCheckable |
413-
QtCore.Qt.ItemIsEnabled)
414-
checkbox.setCheckState(QtCore.Qt.Unchecked)
415-
checkbox.applet_uid = uid
416-
checkbox.applet_dock = None
417-
checkbox.applet_geometry = None
418-
self.table.setItem(row, 0, checkbox)
419-
self.table.setItem(row, 1, QtWidgets.QTableWidgetItem())
420-
self.table.setItem(row, 2, QtWidgets.QTableWidgetItem())
421-
return row
422-
423-
def new_template(self, template):
424-
row = self.new()
425-
self.table.item(row, 2).setText(template)
433+
if name is None:
434+
name = self.get_untitled()
435+
item = QtWidgets.QTreeWidgetItem(["", name, command])
436+
item.ty = "applet"
437+
item.setFlags(QtCore.Qt.ItemIsSelectable |
438+
QtCore.Qt.ItemIsUserCheckable |
439+
QtCore.Qt.ItemIsEditable |
440+
QtCore.Qt.ItemIsDragEnabled |
441+
QtCore.Qt.ItemNeverHasChildren |
442+
QtCore.Qt.ItemIsEnabled)
443+
item.setCheckState(0, QtCore.Qt.Unchecked)
444+
item.applet_uid = uid
445+
item.applet_dock = None
446+
item.applet_geometry = None
447+
item.setIcon(0, QtWidgets.QApplication.style().standardIcon(
448+
QtWidgets.QStyle.SP_ComputerIcon))
449+
if parent is None:
450+
self.table.addTopLevelItem(item)
451+
else:
452+
parent.addChild(item)
453+
return item
454+
455+
def new_group(self, name=None, parent=None):
456+
if name is None:
457+
name = self.get_untitled()
458+
item = QtWidgets.QTreeWidgetItem(["", name])
459+
item.ty = "group"
460+
item.setFlags(QtCore.Qt.ItemIsSelectable |
461+
QtCore.Qt.ItemIsEditable |
462+
QtCore.Qt.ItemIsUserCheckable |
463+
QtCore.Qt.ItemIsAutoTristate |
464+
QtCore.Qt.ItemIsDragEnabled |
465+
QtCore.Qt.ItemIsDropEnabled |
466+
QtCore.Qt.ItemIsEnabled)
467+
item.setIcon(0, QtWidgets.QApplication.style().standardIcon(
468+
QtWidgets.QStyle.SP_DirIcon))
469+
if parent is None:
470+
self.table.addTopLevelItem(item)
471+
else:
472+
parent.addChild(item)
473+
return item
474+
475+
def new_with_parent(self, cb, **kwargs):
476+
parent = None
477+
selection = self.table.selectedItems()
478+
if selection:
479+
parent = selection[0]
480+
if parent.ty == "applet":
481+
parent = parent.parent()
482+
if parent is not None:
483+
parent.setExpanded(True)
484+
cb(parent=parent, **kwargs)
426485

427486
def restart(self):
428-
selection = self.table.selectedRanges()
487+
selection = self.table.selectedItems()
429488
if selection:
430-
row = selection[0].topRow()
431-
dock = self.table.item(row, 0).applet_dock
432-
if dock is not None:
433-
asyncio.ensure_future(dock.restart())
489+
item = selection[0]
490+
def walk(wi):
491+
if wi.ty == "applet":
492+
dock = wi.applet_dock
493+
if dock is not None:
494+
asyncio.ensure_future(dock.restart())
495+
elif wi.ty == "group":
496+
for i in range(wi.childCount()):
497+
walk(wi.child(i))
498+
else:
499+
raise ValueError
500+
walk(item)
434501

435502
def delete(self):
436-
selection = self.table.selectedRanges()
503+
selection = self.table.selectedItems()
437504
if selection:
438-
row = selection[0].topRow()
439-
item = self.table.item(row, 0)
440-
dock = item.applet_dock
441-
if dock is not None:
442-
# This calls self.on_dock_closed
443-
dock.close()
444-
self.applet_uids.remove(item.applet_uid)
445-
self.table.removeRow(row)
505+
item = selection[0]
506+
507+
def recursive_delete(wi):
508+
if wi.ty == "applet":
509+
dock = wi.applet_dock
510+
if dock is not None:
511+
# This calls self.on_dock_closed
512+
dock.close()
513+
self.applet_uids.remove(wi.applet_uid)
514+
elif wi.ty == "group":
515+
for i in range(wi.childCount()):
516+
recursive_delete(wi.child(i))
517+
else:
518+
raise ValueError
519+
recursive_delete(item)
520+
521+
parent = item.parent()
522+
if parent is None:
523+
parent = self.table.invisibleRootItem()
524+
parent.removeChild(item)
446525

447526
async def stop(self):
448-
for row in range(self.table.rowCount()):
449-
dock = self.table.item(row, 0).applet_dock
450-
if dock is not None:
451-
await dock.terminate()
527+
async def walk(wi):
528+
for row in range(wi.childCount()):
529+
cwi = wi.child(row)
530+
if cwi.ty == "applet":
531+
dock = cwi.applet_dock
532+
if dock is not None:
533+
await dock.terminate()
534+
elif cwi.ty == "group":
535+
await walk(cwi)
536+
else:
537+
raise ValueError
538+
await walk(self.table.invisibleRootItem())
452539

453-
def save_state(self):
540+
def save_state_item(self, wi):
454541
state = []
455-
for row in range(self.table.rowCount()):
456-
uid = self.table.item(row, 0).applet_uid
457-
enabled = self.table.item(row, 0).checkState() == QtCore.Qt.Checked
458-
name = self.table.item(row, 1).text()
459-
command = self.table.item(row, 2).text()
460-
geometry = self.table.item(row, 0).applet_geometry
461-
if geometry is not None:
462-
geometry = bytes(geometry)
463-
state.append((uid, enabled, name, command, geometry))
542+
for row in range(wi.childCount()):
543+
cwi = wi.child(row)
544+
if cwi.ty == "applet":
545+
uid = cwi.applet_uid
546+
enabled = cwi.checkState(0) == QtCore.Qt.Checked
547+
name = cwi.text(1)
548+
command = cwi.text(2)
549+
geometry = cwi.applet_geometry
550+
if geometry is not None:
551+
geometry = bytes(geometry)
552+
state.append(("applet", uid, enabled, name, command, geometry))
553+
elif cwi.ty == "group":
554+
name = cwi.text(1)
555+
expanded = cwi.isExpanded()
556+
state_child = self.save_state_item(cwi)
557+
state.append(("group", name, expanded, state_child))
558+
else:
559+
raise ValueError
464560
return state
465561

562+
def save_state(self):
563+
return self.save_state_item(self.table.invisibleRootItem())
564+
565+
def restore_state_item(self, state, parent):
566+
for wis in state:
567+
if wis[0] == "applet":
568+
_, uid, enabled, name, command, geometry = wis
569+
item = self.new(uid, name, command, parent=parent)
570+
if geometry is not None:
571+
geometry = QtCore.QByteArray(geometry)
572+
item.applet_geometry = geometry
573+
if enabled:
574+
item.setCheckState(0, QtCore.Qt.Checked)
575+
elif wis[0] == "group":
576+
_, name, expanded, state_child = wis
577+
item = self.new_group(name, parent=parent)
578+
item.setExpanded(expanded)
579+
self.restore_state_item(state_child, item)
580+
else:
581+
raise ValueError("Invalid item state: " + str(wis[0]))
582+
466583
def restore_state(self, state):
467-
for uid, enabled, name, command, geometry in state:
468-
row = self.new(uid)
469-
item = QtWidgets.QTableWidgetItem()
470-
item.setText(name)
471-
self.table.setItem(row, 1, item)
472-
item = QtWidgets.QTableWidgetItem()
473-
item.setText(command)
474-
self.table.setItem(row, 2, item)
475-
if geometry is not None:
476-
geometry = QtCore.QByteArray(geometry)
477-
self.table.item(row, 0).applet_geometry = geometry
478-
if enabled:
479-
self.table.item(row, 0).setCheckState(QtCore.Qt.Checked)
584+
self.restore_state_item(state, None)

0 commit comments

Comments
 (0)
Please sign in to comment.