forked from M-Labs/artiq
1
0
Fork 0

gui: basic support for opening files outside repository

This commit is contained in:
Sebastien Bourdeauducq 2015-12-08 17:52:38 +08:00
parent 238ee9adb4
commit 0da89557da
3 changed files with 186 additions and 128 deletions

View File

@ -118,10 +118,16 @@ _argty_to_entry = {
} }
# Experiment URLs come in two forms:
# 1. repo:<experiment name>
# (file name and class name to be retrieved from explist)
# 2. file:<class name>@<file name>
class _ArgumentEditor(QtGui.QTreeWidget): class _ArgumentEditor(QtGui.QTreeWidget):
def __init__(self, manager, dock, expname): def __init__(self, manager, dock, expurl):
self.manager = manager self.manager = manager
self.expname = expname self.expurl = expurl
QtGui.QTreeWidget.__init__(self) QtGui.QTreeWidget.__init__(self)
self.setColumnCount(3) self.setColumnCount(3)
@ -137,7 +143,7 @@ class _ArgumentEditor(QtGui.QTreeWidget):
self._groups = dict() self._groups = dict()
self._arg_to_entry_widgetitem = dict() self._arg_to_entry_widgetitem = dict()
arguments = self.manager.get_submission_arguments(self.expname) arguments = self.manager.get_submission_arguments(self.expurl)
if not arguments: if not arguments:
self.addTopLevelItem(QtGui.QTreeWidgetItem(["No arguments"])) self.addTopLevelItem(QtGui.QTreeWidgetItem(["No arguments"]))
@ -194,11 +200,11 @@ class _ArgumentEditor(QtGui.QTreeWidget):
async def _recompute_argument(self, name): async def _recompute_argument(self, name):
try: try:
arginfo = await self.manager.recompute_arginfo(self.expname) arginfo = await self.manager.compute_arginfo(self.expurl)
except: except:
logger.warning("Could not recompute argument '%s' of '%s'", logger.warning("Could not recompute argument '%s' of '%s'",
name, self.expname, exc_info=True) name, self.expurl, exc_info=True)
argument = self.manager.get_submission_arguments(self.expname)[name] argument = self.manager.get_submission_arguments(self.expurl)[name]
procdesc = arginfo[name][0] procdesc = arginfo[name][0]
state = _argty_to_entry[procdesc["ty"]].default_state(procdesc) state = _argty_to_entry[procdesc["ty"]].default_state(procdesc)
@ -228,21 +234,21 @@ class _ArgumentEditor(QtGui.QTreeWidget):
class _ExperimentDock(dockarea.Dock): class _ExperimentDock(dockarea.Dock):
def __init__(self, manager, expname): def __init__(self, manager, expurl):
dockarea.Dock.__init__(self, "Exp: " + expname, dockarea.Dock.__init__(self, "Exp: " + expurl,
closable=True, size=(1500, 500)) closable=True, size=(1500, 500))
self.layout.setSpacing(5) self.layout.setSpacing(5)
self.layout.setContentsMargins(5, 5, 5, 5) self.layout.setContentsMargins(5, 5, 5, 5)
self.manager = manager self.manager = manager
self.expname = expname self.expurl = expurl
self.argeditor = _ArgumentEditor(self.manager, self, self.expname) self.argeditor = _ArgumentEditor(self.manager, self, self.expurl)
self.addWidget(self.argeditor, 0, 0, colspan=5) self.addWidget(self.argeditor, 0, 0, colspan=5)
self.layout.setRowStretch(0, 1) self.layout.setRowStretch(0, 1)
scheduling = manager.get_submission_scheduling(expname) scheduling = manager.get_submission_scheduling(expurl)
options = manager.get_submission_options(expname) options = manager.get_submission_options(expurl)
datetime = QtGui.QDateTimeEdit() datetime = QtGui.QDateTimeEdit()
datetime.setDisplayFormat("MMM d yyyy hh:mm:ss") datetime.setDisplayFormat("MMM d yyyy hh:mm:ss")
@ -312,23 +318,23 @@ class _ExperimentDock(dockarea.Dock):
options["log_level"] = getattr(logging, log_level.currentText()) options["log_level"] = getattr(logging, log_level.currentText())
log_level.currentIndexChanged.connect(update_log_level) log_level.currentIndexChanged.connect(update_log_level)
repo_rev = QtGui.QLineEdit() if "repo_rev" in options:
repo_rev.setPlaceholderText("current") repo_rev = QtGui.QLineEdit()
repo_rev_label = QtGui.QLabel("Revision:") repo_rev.setPlaceholderText("current")
repo_rev_label.setToolTip("Experiment repository revision " repo_rev_label = QtGui.QLabel("Revision:")
"(commit ID) to use") repo_rev_label.setToolTip("Experiment repository revision "
self.addWidget(repo_rev_label, 3, 2) "(commit ID) to use")
self.addWidget(repo_rev, 3, 3) self.addWidget(repo_rev_label, 3, 2)
self.addWidget(repo_rev, 3, 3)
if options["repo_rev"] is not None: if options["repo_rev"] is not None:
repo_rev.setText(options["repo_rev"]) repo_rev.setText(options["repo_rev"])
def update_repo_rev(): def update_repo_rev(text):
t = repo_rev.text() if text:
if t: options["repo_rev"] = text
options["repo_rev"] = t else:
else: options["repo_rev"] = None
options["repo_rev"] = None repo_rev.textEdited.connect(update_repo_rev)
repo_rev.editingFinished.connect(update_repo_rev)
submit = QtGui.QPushButton("Submit") submit = QtGui.QPushButton("Submit")
submit.setIcon(QtGui.QApplication.style().standardIcon( submit.setIcon(QtGui.QApplication.style().standardIcon(
@ -352,35 +358,35 @@ class _ExperimentDock(dockarea.Dock):
def submit_clicked(self): def submit_clicked(self):
try: try:
self.manager.submit(self.expname) self.manager.submit(self.expurl)
except: except:
# May happen when experiment has been removed # May happen when experiment has been removed
# from repository/explist # from repository/explist
logger.warning("failed to submit '%s'", logger.warning("failed to submit '%s'",
self.expname, exc_info=True) self.expurl, exc_info=True)
def reqterm_clicked(self): def reqterm_clicked(self):
try: try:
self.manager.request_inst_term(self.expname) self.manager.request_inst_term(self.expurl)
except: except:
# May happen when experiment has been removed # May happen when experiment has been removed
# from repository/explist # from repository/explist
logger.warning("failed to request termination of instances of '%s'", logger.warning("failed to request termination of instances of '%s'",
self.expname, exc_info=True) self.expurl, exc_info=True)
def _recompute_arguments_clicked(self): def _recompute_arguments_clicked(self):
asyncio.ensure_future(self._recompute_arguments_task()) asyncio.ensure_future(self._recompute_arguments_task())
async def _recompute_arguments_task(self): async def _recompute_arguments_task(self):
try: try:
arginfo = await self.manager.recompute_arginfo(self.expname) arginfo = await self.manager.compute_arginfo(self.expurl)
except: except:
logger.warning("Could not recompute arguments of '%s'", logger.warning("Could not recompute arguments of '%s'",
self.expname, exc_info=True) self.expurl, exc_info=True)
self.manager.initialize_submission_arguments(self.expname, arginfo) self.manager.initialize_submission_arguments(self.expurl, arginfo)
self.argeditor.deleteLater() self.argeditor.deleteLater()
self.argeditor = _ArgumentEditor(self.manager, self, self.expname) self.argeditor = _ArgumentEditor(self.manager, self, self.expurl)
self.addWidget(self.argeditor, 0, 0, colspan=5) self.addWidget(self.argeditor, 0, 0, colspan=5)
def save_state(self): def save_state(self):
@ -416,9 +422,19 @@ class ExperimentManager:
def set_schedule_model(self, model): def set_schedule_model(self, model):
self.schedule = model.backing_store self.schedule = model.backing_store
def get_submission_scheduling(self, expname): def resolve_expurl(self, expurl):
if expname in self.submission_scheduling: if expurl[:5] == "repo:":
return self.submission_scheduling[expname] expinfo = self.explist[expurl[5:]]
return expinfo["file"], expinfo["class_name"], True
elif expurl[:5] == "file:":
class_name, file = expurl[5:].split("@", maxsplit=1)
return file, class_name, False
else:
raise ValueError("Malformed experiment URL")
def get_submission_scheduling(self, expurl):
if expurl in self.submission_scheduling:
return self.submission_scheduling[expurl]
else: else:
# mutated by _ExperimentDock # mutated by _ExperimentDock
scheduling = { scheduling = {
@ -427,22 +443,23 @@ class ExperimentManager:
"due_date": None, "due_date": None,
"flush": False "flush": False
} }
self.submission_scheduling[expname] = scheduling self.submission_scheduling[expurl] = scheduling
return scheduling return scheduling
def get_submission_options(self, expname): def get_submission_options(self, expurl):
if expname in self.submission_options: if expurl in self.submission_options:
return self.submission_options[expname] return self.submission_options[expurl]
else: else:
# mutated by _ExperimentDock # mutated by _ExperimentDock
options = { options = {
"log_level": logging.WARNING, "log_level": logging.WARNING
"repo_rev": None
} }
self.submission_options[expname] = options if expurl[:5] == "repo:":
options["repo_rev"] = None
self.submission_options[expurl] = options
return options return options
def initialize_submission_arguments(self, expname, arginfo): def initialize_submission_arguments(self, expurl, arginfo):
arguments = OrderedDict() arguments = OrderedDict()
for name, (procdesc, group) in arginfo.items(): for name, (procdesc, group) in arginfo.items():
state = _argty_to_entry[procdesc["ty"]].default_state(procdesc) state = _argty_to_entry[procdesc["ty"]].default_state(procdesc)
@ -451,39 +468,42 @@ class ExperimentManager:
"group": group, "group": group,
"state": state # mutated by entries "state": state # mutated by entries
} }
self.submission_arguments[expname] = arguments self.submission_arguments[expurl] = arguments
return arguments return arguments
def get_submission_arguments(self, expname): def get_submission_arguments(self, expurl):
if expname in self.submission_arguments: if expurl in self.submission_arguments:
return self.submission_arguments[expname] return self.submission_arguments[expurl]
else: else:
arginfo = self.explist[expname]["arginfo"] if expurl[:5] != "repo:":
arguments = self.initialize_submission_arguments(arginfo) raise ValueError("Submission arguments must be preinitialized "
"when not using repository")
arginfo = self.explist[expurl[5:]]["arginfo"]
arguments = self.initialize_submission_arguments(expurl, arginfo)
return arguments return arguments
def open_experiment(self, expname): def open_experiment(self, expurl):
if expname in self.open_experiments: if expurl in self.open_experiments:
return self.open_experiments[expname] return self.open_experiments[expurl]
dock = _ExperimentDock(self, expname) dock = _ExperimentDock(self, expurl)
self.open_experiments[expname] = dock self.open_experiments[expurl] = dock
self.dock_area.addDock(dock) self.dock_area.addDock(dock)
self.dock_area.floatDock(dock) self.dock_area.floatDock(dock)
dock.sigClosed.connect(partial(self.on_dock_closed, expname)) dock.sigClosed.connect(partial(self.on_dock_closed, expurl))
return dock return dock
def on_dock_closed(self, expname): def on_dock_closed(self, expurl):
del self.open_experiments[expname] del self.open_experiments[expurl]
async def _submit_task(self, *args): async def _submit_task(self, *args):
rid = await self.schedule_ctl.submit(*args) rid = await self.schedule_ctl.submit(*args)
self.status_bar.showMessage("Submitted RID {}".format(rid)) self.status_bar.showMessage("Submitted RID {}".format(rid))
def submit(self, expname): def submit(self, expurl):
expinfo = self.explist[expname] file, class_name, _ = self.resolve_expurl(expurl)
scheduling = self.get_submission_scheduling(expname) scheduling = self.get_submission_scheduling(expurl)
options = self.get_submission_options(expname) options = self.get_submission_options(expurl)
arguments = self.get_submission_arguments(expname) arguments = self.get_submission_arguments(expurl)
argument_values = dict() argument_values = dict()
for name, argument in arguments.items(): for name, argument in arguments.items():
@ -492,11 +512,12 @@ class ExperimentManager:
expid = { expid = {
"log_level": options["log_level"], "log_level": options["log_level"],
"repo_rev": options["repo_rev"], "file": file,
"file": expinfo["file"], "class_name": class_name,
"class_name": expinfo["class_name"],
"arguments": argument_values, "arguments": argument_values,
} }
if "repo_rev" in options:
expid["repo_rev"] = options["repo_rev"]
asyncio.ensure_future(self._submit_task( asyncio.ensure_future(self._submit_task(
scheduling["pipeline_name"], scheduling["pipeline_name"],
expid, expid,
@ -513,27 +534,41 @@ class ExperimentManager:
logger.debug("failed to request termination of RID %d", logger.debug("failed to request termination of RID %d",
rid, exc_info=True) rid, exc_info=True)
def request_inst_term(self, expname): def request_inst_term(self, expurl):
self.status_bar.showMessage("Requesting termination of all instances " self.status_bar.showMessage("Requesting termination of all instances "
"of '{}'".format(expname)) "of '{}'".format(expurl))
expinfo = self.explist[expname] file, class_name, use_repository = self.resolve_expurl(expurl)
rids = [] rids = []
for rid, desc in self.schedule.items(): for rid, desc in self.schedule.items():
expid = desc["expid"] expid = desc["expid"]
if ("repo_rev" in expid # only consider runs from repository if use_repository:
and expid["file"] == expinfo["file"] repo_match = "repo_rev" in expid
and expid["class_name"] == expinfo["class_name"]): else:
repo_match = "repo_rev" not in expid
if (repo_match
and expid["file"] == file
and expid["class_name"] == class_name):
rids.append(rid) rids.append(rid)
asyncio.ensure_future(self._request_term_multiple(rids)) asyncio.ensure_future(self._request_term_multiple(rids))
async def recompute_arginfo(self, expname): async def compute_arginfo(self, expurl):
expinfo = self.explist[expname] file, class_name, use_repository = self.resolve_expurl(expurl)
description = await self.experiment_db_ctl.examine(expinfo["file"]) description = await self.experiment_db_ctl.examine(file,
return description[expinfo["class_name"]]["arginfo"] use_repository)
return description[class_name]["arginfo"]
async def open_file(self, file):
description = await self.experiment_db_ctl.examine(file, False)
for class_name, class_desc in description.items():
expurl = "file:{}@{}".format(class_name, file)
self.initialize_submission_arguments(expurl, class_desc["arginfo"])
if expurl in self.open_experiments:
self.open_experiments[expurl].close()
self.open_experiment(expurl)
def save_state(self): def save_state(self):
docks = {expname: dock.save_state() docks = {expurl: dock.save_state()
for expname, dock in self.open_experiments.items()} for expurl, dock in self.open_experiments.items()}
return { return {
"scheduling": self.submission_scheduling, "scheduling": self.submission_scheduling,
"options": self.submission_options, "options": self.submission_options,
@ -547,6 +582,6 @@ class ExperimentManager:
self.submission_scheduling = state["scheduling"] self.submission_scheduling = state["scheduling"]
self.submission_options = state["options"] self.submission_options = state["options"]
self.submission_arguments = state["arguments"] self.submission_arguments = state["arguments"]
for expname, dock_state in state["docks"].items(): for expurl, dock_state in state["docks"].items():
dock = self.open_experiment(expname) dock = self.open_experiment(expurl)
dock.restore_state(dock_state) dock.restore_state(dock_state)

View File

@ -9,17 +9,34 @@ from pyqtgraph import LayoutWidget
from artiq.gui.models import DictSyncTreeSepModel from artiq.gui.models import DictSyncTreeSepModel
class _OpenFileDialog(QtGui.QDialog):
def __init__(self, parent, exp_manager):
QtGui.QDialog.__init__(self, parent=parent)
self.setWindowTitle("Open file outside repository")
grid = QtGui.QGridLayout()
self.setLayout(grid)
grid.addWidget(QtGui.QLabel("Filename:"), 0, 0)
filename = QtGui.QLineEdit()
grid.addWidget(filename, 0, 1)
buttons = QtGui.QDialogButtonBox(
QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel)
grid.addWidget(buttons, 1, 0, 1, 2)
buttons.accepted.connect(self.accept)
buttons.rejected.connect(self.reject)
def open_file():
file = filename.text()
asyncio.ensure_future(exp_manager.open_file(file))
self.accepted.connect(open_file)
class Model(DictSyncTreeSepModel): class Model(DictSyncTreeSepModel):
def __init__(self, init): def __init__(self, init):
self.explorer = None
DictSyncTreeSepModel.__init__(self, "/", ["Experiment"], init) DictSyncTreeSepModel.__init__(self, "/", ["Experiment"], init)
def __setitem__(self, k, v):
DictSyncTreeSepModel.__setitem__(self, k, v)
# TODO
#if self.explorer is not None and k == self.explorer.selected_key:
# self.explorer.update_selection(k, k)
class ExplorerDock(dockarea.Dock): class ExplorerDock(dockarea.Dock):
def __init__(self, status_bar, exp_manager, d_shortcuts, def __init__(self, status_bar, exp_manager, d_shortcuts,
@ -37,43 +54,45 @@ class ExplorerDock(dockarea.Dock):
self.el.setHeaderHidden(True) self.el.setHeaderHidden(True)
self.el.setSelectionBehavior(QtGui.QAbstractItemView.SelectItems) self.el.setSelectionBehavior(QtGui.QAbstractItemView.SelectItems)
self.addWidget(self.el, 0, 0, colspan=2) self.addWidget(self.el, 0, 0, colspan=2)
self.el.doubleClicked.connect(self.open_clicked) self.el.doubleClicked.connect(
partial(self.expname_action, "open_experiment"))
open = QtGui.QPushButton("Open") open = QtGui.QPushButton("Open")
open.setIcon(QtGui.QApplication.style().standardIcon( open.setIcon(QtGui.QApplication.style().standardIcon(
QtGui.QStyle.SP_DialogOpenButton)) QtGui.QStyle.SP_DialogOpenButton))
open.setToolTip("Open the selected experiment (Return)") open.setToolTip("Open the selected experiment (Return)")
self.addWidget(open, 1, 0) self.addWidget(open, 1, 0)
open.clicked.connect(self.open_clicked) open.clicked.connect(
partial(self.expname_action, "open_experiment"))
submit = QtGui.QPushButton("Submit") submit = QtGui.QPushButton("Submit")
submit.setIcon(QtGui.QApplication.style().standardIcon( submit.setIcon(QtGui.QApplication.style().standardIcon(
QtGui.QStyle.SP_DialogOkButton)) QtGui.QStyle.SP_DialogOkButton))
submit.setToolTip("Schedule the selected experiment (Ctrl+Return)") submit.setToolTip("Schedule the selected experiment (Ctrl+Return)")
self.addWidget(submit, 1, 1) self.addWidget(submit, 1, 1)
submit.clicked.connect(self.submit_clicked) submit.clicked.connect(
partial(self.expname_action, "submit"))
self.explist_model = Model(dict()) self.explist_model = Model(dict())
explist_sub.add_setmodel_callback(self.set_model) explist_sub.add_setmodel_callback(self.set_model)
self.el.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu) self.el.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
open_action = QtGui.QAction("Open", self.el) open_action = QtGui.QAction("Open", self.el)
open_action.triggered.connect(self.open_clicked) open_action.triggered.connect(
partial(self.expname_action, "open_experiment"))
open_action.setShortcut("RETURN") open_action.setShortcut("RETURN")
self.el.addAction(open_action) self.el.addAction(open_action)
submit_action = QtGui.QAction("Submit", self.el) submit_action = QtGui.QAction("Submit", self.el)
submit_action.triggered.connect(self.submit_clicked) submit_action.triggered.connect(
partial(self.expname_action, "submit"))
submit_action.setShortcut("CTRL+RETURN") submit_action.setShortcut("CTRL+RETURN")
self.el.addAction(submit_action) self.el.addAction(submit_action)
reqterm_action = QtGui.QAction("Request termination of instances", self.el) reqterm_action = QtGui.QAction("Request termination of instances", self.el)
reqterm_action.triggered.connect(self.reqterm_clicked) reqterm_action.triggered.connect(
partial(self.expname_action, "request_inst_term"))
reqterm_action.setShortcut("CTRL+BACKSPACE") reqterm_action.setShortcut("CTRL+BACKSPACE")
self.el.addAction(reqterm_action) self.el.addAction(reqterm_action)
sep = QtGui.QAction(self.el)
sep.setSeparator(True)
self.el.addAction(sep)
set_shortcut_menu = QtGui.QMenu() set_shortcut_menu = QtGui.QMenu()
for i in range(12): for i in range(12):
action = QtGui.QAction("F" + str(i+1), self.el) action = QtGui.QAction("F" + str(i+1), self.el)
@ -83,7 +102,12 @@ class ExplorerDock(dockarea.Dock):
set_shortcut_action = QtGui.QAction("Set shortcut", self.el) set_shortcut_action = QtGui.QAction("Set shortcut", self.el)
set_shortcut_action.setMenu(set_shortcut_menu) set_shortcut_action.setMenu(set_shortcut_menu)
self.el.addAction(set_shortcut_action) self.el.addAction(set_shortcut_action)
scan_repository_action = QtGui.QAction("(Re)scan repository HEAD",
sep = QtGui.QAction(self.el)
sep.setSeparator(True)
self.el.addAction(sep)
scan_repository_action = QtGui.QAction("Scan repository HEAD",
self.el) self.el)
def scan_repository(): def scan_repository():
asyncio.ensure_future(experiment_db_ctl.scan_repository_async()) asyncio.ensure_future(experiment_db_ctl.scan_repository_async())
@ -91,8 +115,13 @@ class ExplorerDock(dockarea.Dock):
scan_repository_action.triggered.connect(scan_repository) scan_repository_action.triggered.connect(scan_repository)
self.el.addAction(scan_repository_action) self.el.addAction(scan_repository_action)
open_file_action = QtGui.QAction("Open file outside repository",
self.el)
open_file_action.triggered.connect(
lambda: _OpenFileDialog(self, self.exp_manager).open())
self.el.addAction(open_file_action)
def set_model(self, model): def set_model(self, model):
model.explorer = self
self.explist_model = model self.explist_model = model
self.el.setModel(model) self.el.setModel(model)
@ -103,22 +132,16 @@ class ExplorerDock(dockarea.Dock):
else: else:
return None return None
def open_clicked(self): def expname_action(self, action):
expname = self._get_selected_expname() expname = self._get_selected_expname()
if expname is not None: if expname is not None:
self.exp_manager.open_experiment(expname) action = getattr(self.exp_manager, action)
action("repo:" + expname)
def submit_clicked(self):
expname = self._get_selected_expname()
if expname is not None:
self.exp_manager.submit(expname)
def reqterm_clicked(self):
expname = self._get_selected_expname()
if expname is not None:
self.exp_manager.request_inst_term(expname)
def set_shortcut(self, nr): def set_shortcut(self, nr):
expname = self._get_selected_expname() expname = self._get_selected_expname()
if expname is not None: if expname is not None:
self.d_shortcuts.set_shortcut(nr, expname) expurl = "repo:" + expname
self.d_shortcuts.set_shortcut(nr, expurl)
self.status_bar.showMessage("Set shortcut F{} to '{}'"
.format(nr+1, expurl))

View File

@ -71,31 +71,31 @@ class ShortcutsDock(dockarea.Dock):
shortcut.activated.connect(partial(self._activated, i)) shortcut.activated.connect(partial(self._activated, i))
def _activated(self, nr): def _activated(self, nr):
expname = self.shortcut_widgets[nr]["label"].text() expurl = self.shortcut_widgets[nr]["label"].text()
if expname: if expurl:
try: try:
self.exp_manager.submit(expname) self.exp_manager.submit(expurl)
except: except:
# May happen when experiment has been removed # May happen when experiment has been removed
# from repository/explist # from repository/explist
logger.warning("failed to submit experiment %s", logger.warning("failed to submit experiment %s",
expname, exc_info=True) expurl, exc_info=True)
def _open_experiment(self, nr): def _open_experiment(self, nr):
expname = self.shortcut_widgets[nr]["label"].text() expurl = self.shortcut_widgets[nr]["label"].text()
if expname: if expurl:
try: try:
self.exp_manager.open_experiment(expname) self.exp_manager.open_experiment(expurl)
except: except:
# May happen when experiment has been removed # May happen when experiment has been removed
# from repository/explist # from repository/explist
logger.warning("failed to open experiment %s", logger.warning("failed to open experiment %s",
expname, exc_info=True) expurl, exc_info=True)
def set_shortcut(self, nr, expname): def set_shortcut(self, nr, expurl):
widgets = self.shortcut_widgets[nr] widgets = self.shortcut_widgets[nr]
widgets["label"].setText(expname) widgets["label"].setText(expurl)
if expname: if expurl:
widgets["clear"].show() widgets["clear"].show()
widgets["open"].show() widgets["open"].show()
widgets["submit"].show() widgets["submit"].show()
@ -109,5 +109,5 @@ class ShortcutsDock(dockarea.Dock):
for nr, widgets in self.shortcut_widgets.items()} for nr, widgets in self.shortcut_widgets.items()}
def restore_state(self, state): def restore_state(self, state):
for nr, expname in state.items(): for nr, expurl in state.items():
self.set_shortcut(nr, expname) self.set_shortcut(nr, expurl)