artiq/artiq/dashboard/explorer.py

315 lines
12 KiB
Python
Raw Normal View History

2015-05-24 20:24:07 +08:00
import asyncio
2015-10-20 18:11:50 +08:00
import logging
import re
from functools import partial
2015-05-24 20:24:07 +08:00
2016-02-15 07:23:47 +08:00
from PyQt5 import QtCore, QtWidgets
2016-02-15 07:05:30 +08:00
from artiq.gui.tools import LayoutWidget
2015-11-17 19:46:26 +08:00
from artiq.gui.models import DictSyncTreeSepModel
from artiq.gui.waitingspinnerwidget import QtWaitingSpinner
2015-05-24 20:24:07 +08:00
2015-12-08 18:57:53 +08:00
logger = logging.getLogger(__name__)
2016-02-15 07:23:47 +08:00
class _OpenFileDialog(QtWidgets.QDialog):
def __init__(self, explorer, exp_manager, experiment_db_ctl):
2016-02-15 07:23:47 +08:00
QtWidgets.QDialog.__init__(self, parent=explorer)
self.resize(710, 700)
self.setWindowTitle("Open file outside repository")
self.explorer = explorer
2015-12-08 18:57:53 +08:00
self.exp_manager = exp_manager
self.experiment_db_ctl = experiment_db_ctl
2015-12-08 18:57:53 +08:00
2016-02-15 07:23:47 +08:00
grid = QtWidgets.QGridLayout()
self.setLayout(grid)
2016-02-15 07:23:47 +08:00
grid.addWidget(QtWidgets.QLabel("Location:"), 0, 0)
self.location_label = QtWidgets.QLabel("")
grid.addWidget(self.location_label, 0, 1)
grid.setColumnStretch(1, 1)
2016-02-15 07:23:47 +08:00
self.file_list = QtWidgets.QListWidget()
asyncio.ensure_future(self.refresh_view())
grid.addWidget(self.file_list, 1, 0, 1, 2)
self.file_list.doubleClicked.connect(self.accept)
2016-02-15 07:23:47 +08:00
buttons = QtWidgets.QDialogButtonBox(
QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel)
grid.addWidget(buttons, 2, 0, 1, 2)
buttons.accepted.connect(self.accept)
buttons.rejected.connect(self.reject)
async def refresh_view(self):
self.file_list.clear()
if not self.explorer.current_directory:
self.location_label.setText("<root>")
else:
self.location_label.setText(self.explorer.current_directory)
2016-02-15 07:23:47 +08:00
item = QtWidgets.QListWidgetItem()
item.setText("..")
2016-02-15 07:23:47 +08:00
item.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_FileDialogToParent))
self.file_list.addItem(item)
try:
contents = await self.experiment_db_ctl.list_directory(
self.explorer.current_directory)
except:
logger.error("Failed to list directory '%s'",
self.explorer.current_directory, exc_info=True)
return
for name in sorted(contents, key=lambda x: (x[-1] not in "\\/", x)):
if name[-1] in "\\/":
2016-02-15 07:23:47 +08:00
icon = QtWidgets.QStyle.SP_DirIcon
else:
2016-02-15 07:23:47 +08:00
icon = QtWidgets.QStyle.SP_FileIcon
if name[-3:] != ".py":
continue
2016-02-15 07:23:47 +08:00
item = QtWidgets.QListWidgetItem()
item.setText(name)
2016-02-15 07:23:47 +08:00
item.setIcon(QtWidgets.QApplication.style().standardIcon(icon))
self.file_list.addItem(item)
def accept(self):
selected = self.file_list.selectedItems()
if selected:
selected = selected[0].text()
if selected == "..":
if not self.explorer.current_directory:
return
if re.fullmatch("[a-zA-Z]:\\\\",
self.explorer.current_directory):
self.explorer.current_directory = ""
else:
idx = None
for sep in "\\/":
try:
idx = self.explorer.current_directory[:-1].rindex(sep)
except ValueError:
pass
else:
break
self.explorer.current_directory = \
self.explorer.current_directory[:idx+1]
if self.explorer.current_directory == "/":
self.explorer.current_directory = ""
asyncio.ensure_future(self.refresh_view())
elif selected[-1] in "\\/":
self.explorer.current_directory += selected
asyncio.ensure_future(self.refresh_view())
else:
file = self.explorer.current_directory + selected
async def open_task():
try:
await self.exp_manager.open_file(file)
except:
logger.error("Failed to open file '%s'",
file, exc_info=True)
asyncio.ensure_future(open_task())
2016-02-15 07:23:47 +08:00
QtWidgets.QDialog.accept(self)
2015-11-17 19:46:26 +08:00
class Model(DictSyncTreeSepModel):
2015-11-11 12:13:19 +08:00
def __init__(self, init):
2015-11-27 19:30:05 +08:00
DictSyncTreeSepModel.__init__(self, "/", ["Experiment"], init)
2015-05-24 20:24:07 +08:00
def convert_tooltip(self, k, v, column):
return ("<b>File:</b> {file}<br><b>Class:</b> {cls}"
.format(file=v["file"], cls=v["class_name"]))
2015-07-18 03:28:46 +08:00
class StatusUpdater:
def __init__(self, init):
self.status = init
self.explorer = None
def set_explorer(self, explorer):
self.explorer = explorer
self.explorer.update_scanning(self.status["scanning"])
self.explorer.update_cur_rev(self.status["cur_rev"])
def __setitem__(self, k, v):
self.status[k] = v
if self.explorer is not None:
if k == "scanning":
self.explorer.update_scanning(v)
elif k == "cur_rev":
self.explorer.update_cur_rev(v)
class WaitingPanel(LayoutWidget):
def __init__(self):
LayoutWidget.__init__(self)
self.waiting_spinner = QtWaitingSpinner()
self.addWidget(self.waiting_spinner, 1, 1)
self.addWidget(QtWidgets.QLabel("Repository scan in progress..."), 1, 2)
def start(self):
self.waiting_spinner.start()
def stop(self):
self.waiting_spinner.stop()
class ExplorerDock(QtWidgets.QDockWidget):
def __init__(self, exp_manager, d_shortcuts,
explist_sub, explist_status_sub,
schedule_ctl, experiment_db_ctl, device_db_ctl):
QtWidgets.QDockWidget.__init__(self, "Explorer")
self.setObjectName("Explorer")
self.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable |
QtWidgets.QDockWidget.DockWidgetFloatable)
top_widget = LayoutWidget()
self.setWidget(top_widget)
2015-05-23 01:25:33 +08:00
2015-11-27 19:30:05 +08:00
self.exp_manager = exp_manager
self.d_shortcuts = d_shortcuts
2015-05-24 20:24:07 +08:00
self.schedule_ctl = schedule_ctl
top_widget.addWidget(QtWidgets.QLabel("Revision:"), 0, 0)
self.revision = QtWidgets.QLabel()
self.revision.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse)
top_widget.addWidget(self.revision, 0, 1)
self.stack = QtWidgets.QStackedWidget()
top_widget.addWidget(self.stack, 1, 0, colspan=2)
self.el_buttons = LayoutWidget()
self.el_buttons.layout.setContentsMargins(0, 0, 0, 0)
self.stack.addWidget(self.el_buttons)
2016-02-15 07:23:47 +08:00
self.el = QtWidgets.QTreeView()
2015-11-17 19:46:26 +08:00
self.el.setHeaderHidden(True)
2016-02-15 07:23:47 +08:00
self.el.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectItems)
self.el.doubleClicked.connect(
partial(self.expname_action, "open_experiment"))
self.el_buttons.addWidget(self.el, 0, 0, colspan=2)
2015-10-20 18:11:50 +08:00
2016-02-15 07:23:47 +08:00
open = QtWidgets.QPushButton("Open")
open.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_DialogOpenButton))
2015-11-27 19:30:05 +08:00
open.setToolTip("Open the selected experiment (Return)")
self.el_buttons.addWidget(open, 1, 0)
open.clicked.connect(
partial(self.expname_action, "open_experiment"))
2015-05-23 01:25:33 +08:00
2016-02-15 07:23:47 +08:00
submit = QtWidgets.QPushButton("Submit")
submit.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_DialogOkButton))
submit.setToolTip("Schedule the selected experiment (Ctrl+Return)")
self.el_buttons.addWidget(submit, 1, 1)
submit.clicked.connect(
partial(self.expname_action, "submit"))
2015-05-23 01:25:33 +08:00
2015-11-11 12:13:19 +08:00
self.explist_model = Model(dict())
explist_sub.add_setmodel_callback(self.set_model)
2015-10-27 17:59:34 +08:00
self.el.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
2016-02-15 07:23:47 +08:00
open_action = QtWidgets.QAction("Open", self.el)
open_action.triggered.connect(
partial(self.expname_action, "open_experiment"))
2015-11-27 19:30:05 +08:00
open_action.setShortcut("RETURN")
open_action.setShortcutContext(QtCore.Qt.WidgetShortcut)
2015-11-27 19:30:05 +08:00
self.el.addAction(open_action)
2016-02-15 07:23:47 +08:00
submit_action = QtWidgets.QAction("Submit", self.el)
submit_action.triggered.connect(
partial(self.expname_action, "submit"))
submit_action.setShortcut("CTRL+RETURN")
submit_action.setShortcutContext(QtCore.Qt.WidgetShortcut)
self.el.addAction(submit_action)
2016-02-15 07:23:47 +08:00
reqterm_action = QtWidgets.QAction("Request termination of instances", self.el)
reqterm_action.triggered.connect(
partial(self.expname_action, "request_inst_term"))
reqterm_action.setShortcut("CTRL+BACKSPACE")
reqterm_action.setShortcutContext(QtCore.Qt.WidgetShortcut)
self.el.addAction(reqterm_action)
2016-02-15 07:23:47 +08:00
set_shortcut_menu = QtWidgets.QMenu()
for i in range(12):
2016-02-15 07:23:47 +08:00
action = QtWidgets.QAction("F" + str(i+1), self.el)
action.triggered.connect(partial(self.set_shortcut, i))
set_shortcut_menu.addAction(action)
2016-02-15 07:23:47 +08:00
set_shortcut_action = QtWidgets.QAction("Set shortcut", self.el)
set_shortcut_action.setMenu(set_shortcut_menu)
self.el.addAction(set_shortcut_action)
2016-02-15 07:23:47 +08:00
sep = QtWidgets.QAction(self.el)
sep.setSeparator(True)
self.el.addAction(sep)
2016-02-15 07:23:47 +08:00
scan_repository_action = QtWidgets.QAction("Scan repository HEAD",
self.el)
def scan_repository():
2015-12-06 18:39:27 +08:00
asyncio.ensure_future(experiment_db_ctl.scan_repository_async())
scan_repository_action.triggered.connect(scan_repository)
self.el.addAction(scan_repository_action)
2015-07-18 03:28:46 +08:00
scan_ddb_action = QtWidgets.QAction("Scan device database", self.el)
def scan_ddb():
asyncio.ensure_future(device_db_ctl.scan())
scan_ddb_action.triggered.connect(scan_ddb)
self.el.addAction(scan_ddb_action)
self.current_directory = ""
2016-02-15 07:23:47 +08:00
open_file_action = QtWidgets.QAction("Open file outside repository",
self.el)
open_file_action.triggered.connect(
lambda: _OpenFileDialog(self, self.exp_manager,
experiment_db_ctl).open())
self.el.addAction(open_file_action)
self.waiting_panel = WaitingPanel()
self.stack.addWidget(self.waiting_panel)
explist_status_sub.add_setmodel_callback(
lambda updater: updater.set_explorer(self))
2015-11-11 12:13:19 +08:00
def set_model(self, model):
self.explist_model = model
self.el.setModel(model)
2015-11-27 19:30:05 +08:00
def _get_selected_expname(self):
selection = self.el.selectedIndexes()
if selection:
2015-11-17 19:46:26 +08:00
return self.explist_model.index_to_key(selection[0])
else:
return None
def expname_action(self, action):
2015-11-27 19:30:05 +08:00
expname = self._get_selected_expname()
if expname is not None:
action = getattr(self.exp_manager, action)
action("repo:" + expname)
2015-05-24 20:24:07 +08:00
def set_shortcut(self, nr):
expname = self._get_selected_expname()
if expname is not None:
expurl = "repo:" + expname
self.d_shortcuts.set_shortcut(nr, expurl)
logger.info("Set shortcut F%d to '%s'", nr+1, expurl)
def update_scanning(self, scanning):
if scanning:
self.stack.setCurrentWidget(self.waiting_panel)
self.waiting_panel.start()
else:
self.stack.setCurrentWidget(self.el_buttons)
self.waiting_panel.stop()
def update_cur_rev(self, cur_rev):
self.revision.setText(cur_rev)
def save_state(self):
return {
"current_directory": self.current_directory
}
def restore_state(self, state):
self.current_directory = state["current_directory"]