forked from M-Labs/artiq
gui: display repository scanning status and revision. Closes #274
This commit is contained in:
parent
4bf0db32be
commit
2859382e11
|
@ -91,11 +91,12 @@ def main():
|
||||||
rpc_clients[target] = client
|
rpc_clients[target] = client
|
||||||
|
|
||||||
sub_clients = dict()
|
sub_clients = dict()
|
||||||
for notifier_name, module in (("explist", explorer),
|
for notifier_name, modelf in (("explist", explorer.Model),
|
||||||
("datasets", datasets),
|
("explist_status", explorer.StatusUpdater),
|
||||||
("schedule", schedule),
|
("datasets", datasets.Model),
|
||||||
("log", log)):
|
("schedule", schedule.Model),
|
||||||
subscriber = ModelSubscriber(notifier_name, module.Model)
|
("log", log.Model)):
|
||||||
|
subscriber = ModelSubscriber(notifier_name, modelf)
|
||||||
loop.run_until_complete(subscriber.connect(
|
loop.run_until_complete(subscriber.connect(
|
||||||
args.server, args.port_notify))
|
args.server, args.port_notify))
|
||||||
atexit_register_coroutine(subscriber.close)
|
atexit_register_coroutine(subscriber.close)
|
||||||
|
@ -123,6 +124,7 @@ def main():
|
||||||
smgr.register(d_shortcuts)
|
smgr.register(d_shortcuts)
|
||||||
d_explorer = explorer.ExplorerDock(status_bar, expmgr, d_shortcuts,
|
d_explorer = explorer.ExplorerDock(status_bar, expmgr, d_shortcuts,
|
||||||
sub_clients["explist"],
|
sub_clients["explist"],
|
||||||
|
sub_clients["explist_status"],
|
||||||
rpc_clients["schedule"],
|
rpc_clients["schedule"],
|
||||||
rpc_clients["experiment_db"])
|
rpc_clients["experiment_db"])
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ from PyQt5 import QtCore, QtWidgets
|
||||||
|
|
||||||
from artiq.gui.tools import LayoutWidget
|
from artiq.gui.tools import LayoutWidget
|
||||||
from artiq.gui.models import DictSyncTreeSepModel
|
from artiq.gui.models import DictSyncTreeSepModel
|
||||||
|
from artiq.gui.waitingspinnerwidget import QtWaitingSpinner
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -117,38 +118,81 @@ class Model(DictSyncTreeSepModel):
|
||||||
DictSyncTreeSepModel.__init__(self, "/", ["Experiment"], init)
|
DictSyncTreeSepModel.__init__(self, "/", ["Experiment"], init)
|
||||||
|
|
||||||
|
|
||||||
|
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):
|
class ExplorerDock(QtWidgets.QDockWidget):
|
||||||
def __init__(self, status_bar, exp_manager, d_shortcuts,
|
def __init__(self, status_bar, exp_manager, d_shortcuts,
|
||||||
explist_sub, schedule_ctl, experiment_db_ctl):
|
explist_sub, explist_status_sub,
|
||||||
|
schedule_ctl, experiment_db_ctl):
|
||||||
QtWidgets.QDockWidget.__init__(self, "Explorer")
|
QtWidgets.QDockWidget.__init__(self, "Explorer")
|
||||||
self.setObjectName("Explorer")
|
self.setObjectName("Explorer")
|
||||||
self.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable |
|
self.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable |
|
||||||
QtWidgets.QDockWidget.DockWidgetFloatable)
|
QtWidgets.QDockWidget.DockWidgetFloatable)
|
||||||
|
|
||||||
layout = QtWidgets.QGridLayout()
|
top_widget = LayoutWidget()
|
||||||
top_widget = QtWidgets.QWidget()
|
|
||||||
top_widget.setLayout(layout)
|
|
||||||
self.setWidget(top_widget)
|
self.setWidget(top_widget)
|
||||||
layout.setSpacing(5)
|
|
||||||
layout.setContentsMargins(5, 5, 5, 5)
|
|
||||||
|
|
||||||
self.status_bar = status_bar
|
self.status_bar = status_bar
|
||||||
self.exp_manager = exp_manager
|
self.exp_manager = exp_manager
|
||||||
self.d_shortcuts = d_shortcuts
|
self.d_shortcuts = d_shortcuts
|
||||||
self.schedule_ctl = schedule_ctl
|
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)
|
||||||
|
|
||||||
self.el = QtWidgets.QTreeView()
|
self.el = QtWidgets.QTreeView()
|
||||||
self.el.setHeaderHidden(True)
|
self.el.setHeaderHidden(True)
|
||||||
self.el.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectItems)
|
self.el.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectItems)
|
||||||
layout.addWidget(self.el, 0, 0, 1, 2)
|
|
||||||
self.el.doubleClicked.connect(
|
self.el.doubleClicked.connect(
|
||||||
partial(self.expname_action, "open_experiment"))
|
partial(self.expname_action, "open_experiment"))
|
||||||
|
self.el_buttons.addWidget(self.el, 0, 0, colspan=2)
|
||||||
|
|
||||||
open = QtWidgets.QPushButton("Open")
|
open = QtWidgets.QPushButton("Open")
|
||||||
open.setIcon(QtWidgets.QApplication.style().standardIcon(
|
open.setIcon(QtWidgets.QApplication.style().standardIcon(
|
||||||
QtWidgets.QStyle.SP_DialogOpenButton))
|
QtWidgets.QStyle.SP_DialogOpenButton))
|
||||||
open.setToolTip("Open the selected experiment (Return)")
|
open.setToolTip("Open the selected experiment (Return)")
|
||||||
layout.addWidget(open, 1, 0)
|
self.el_buttons.addWidget(open, 1, 0)
|
||||||
open.clicked.connect(
|
open.clicked.connect(
|
||||||
partial(self.expname_action, "open_experiment"))
|
partial(self.expname_action, "open_experiment"))
|
||||||
|
|
||||||
|
@ -156,7 +200,7 @@ class ExplorerDock(QtWidgets.QDockWidget):
|
||||||
submit.setIcon(QtWidgets.QApplication.style().standardIcon(
|
submit.setIcon(QtWidgets.QApplication.style().standardIcon(
|
||||||
QtWidgets.QStyle.SP_DialogOkButton))
|
QtWidgets.QStyle.SP_DialogOkButton))
|
||||||
submit.setToolTip("Schedule the selected experiment (Ctrl+Return)")
|
submit.setToolTip("Schedule the selected experiment (Ctrl+Return)")
|
||||||
layout.addWidget(submit, 1, 1)
|
self.el_buttons.addWidget(submit, 1, 1)
|
||||||
submit.clicked.connect(
|
submit.clicked.connect(
|
||||||
partial(self.expname_action, "submit"))
|
partial(self.expname_action, "submit"))
|
||||||
|
|
||||||
|
@ -201,7 +245,6 @@ class ExplorerDock(QtWidgets.QDockWidget):
|
||||||
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())
|
||||||
self.status_bar.showMessage("Requested repository scan")
|
|
||||||
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)
|
||||||
|
|
||||||
|
@ -213,6 +256,11 @@ class ExplorerDock(QtWidgets.QDockWidget):
|
||||||
experiment_db_ctl).open())
|
experiment_db_ctl).open())
|
||||||
self.el.addAction(open_file_action)
|
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))
|
||||||
|
|
||||||
def set_model(self, model):
|
def set_model(self, model):
|
||||||
self.explist_model = model
|
self.explist_model = model
|
||||||
self.el.setModel(model)
|
self.el.setModel(model)
|
||||||
|
@ -237,3 +285,14 @@ class ExplorerDock(QtWidgets.QDockWidget):
|
||||||
self.d_shortcuts.set_shortcut(nr, expurl)
|
self.d_shortcuts.set_shortcut(nr, expurl)
|
||||||
self.status_bar.showMessage("Set shortcut F{} to '{}'"
|
self.status_bar.showMessage("Set shortcut F{} to '{}'"
|
||||||
.format(nr+1, expurl))
|
.format(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)
|
||||||
|
|
|
@ -0,0 +1,186 @@
|
||||||
|
"""
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2012-2014 Alexander Turkin
|
||||||
|
Copyright (c) 2014 William Hallatt
|
||||||
|
Copyright (c) 2015 Jacob Dawid
|
||||||
|
Copyright (c) 2016 Luca Weiss
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import math
|
||||||
|
|
||||||
|
from PyQt5.QtCore import *
|
||||||
|
from PyQt5.QtGui import *
|
||||||
|
from PyQt5.QtWidgets import *
|
||||||
|
|
||||||
|
|
||||||
|
class QtWaitingSpinner(QWidget):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
# WAS IN initialize()
|
||||||
|
self._color = QColor(Qt.black)
|
||||||
|
self._roundness = 100.0
|
||||||
|
self._minimumTrailOpacity = 3.14159265358979323846
|
||||||
|
self._trailFadePercentage = 80.0
|
||||||
|
self._revolutionsPerSecond = 1.57079632679489661923
|
||||||
|
self._numberOfLines = 20
|
||||||
|
self._lineLength = 10
|
||||||
|
self._lineWidth = 2
|
||||||
|
self._innerRadius = 10
|
||||||
|
self._currentCounter = 0
|
||||||
|
|
||||||
|
self._timer = QTimer(self)
|
||||||
|
self._timer.timeout.connect(self.rotate)
|
||||||
|
self.updateSize()
|
||||||
|
self.updateTimer()
|
||||||
|
# END initialize()
|
||||||
|
|
||||||
|
self.setAttribute(Qt.WA_TranslucentBackground)
|
||||||
|
|
||||||
|
def paintEvent(self, QPaintEvent):
|
||||||
|
painter = QPainter(self)
|
||||||
|
painter.fillRect(self.rect(), Qt.transparent)
|
||||||
|
painter.setRenderHint(QPainter.Antialiasing, True)
|
||||||
|
|
||||||
|
if self._currentCounter >= self._numberOfLines:
|
||||||
|
self._currentCounter = 0
|
||||||
|
|
||||||
|
painter.setPen(Qt.NoPen)
|
||||||
|
for i in range(0, self._numberOfLines):
|
||||||
|
painter.save()
|
||||||
|
painter.translate(self._innerRadius + self._lineLength, self._innerRadius + self._lineLength)
|
||||||
|
rotateAngle = float(360 * i) / float(self._numberOfLines)
|
||||||
|
painter.rotate(rotateAngle)
|
||||||
|
painter.translate(self._innerRadius, 0)
|
||||||
|
distance = self.lineCountDistanceFromPrimary(i, self._currentCounter, self._numberOfLines)
|
||||||
|
color = self.currentLineColor(distance, self._numberOfLines, self._trailFadePercentage,
|
||||||
|
self._minimumTrailOpacity, self._color)
|
||||||
|
painter.setBrush(color)
|
||||||
|
painter.drawRoundedRect(QRect(0, -self._lineWidth / 2, self._lineLength, self._lineWidth), self._roundness,
|
||||||
|
self._roundness, Qt.RelativeSize)
|
||||||
|
painter.restore()
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
if not self._timer.isActive():
|
||||||
|
self._timer.start()
|
||||||
|
self._currentCounter = 0
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
if self._timer.isActive():
|
||||||
|
self._timer.stop()
|
||||||
|
self._currentCounter = 0
|
||||||
|
|
||||||
|
def setNumberOfLines(self, lines):
|
||||||
|
self._numberOfLines = lines
|
||||||
|
self._currentCounter = 0
|
||||||
|
self.updateTimer()
|
||||||
|
|
||||||
|
def setLineLength(self, length):
|
||||||
|
self._lineLength = length
|
||||||
|
self.updateSize()
|
||||||
|
|
||||||
|
def setLineWidth(self, width):
|
||||||
|
self._lineWidth = width
|
||||||
|
self.updateSize()
|
||||||
|
|
||||||
|
def setInnerRadius(self, radius):
|
||||||
|
self._innerRadius = radius
|
||||||
|
self.updateSize()
|
||||||
|
|
||||||
|
def color(self):
|
||||||
|
return self._color
|
||||||
|
|
||||||
|
def roundness(self):
|
||||||
|
return self._roundness
|
||||||
|
|
||||||
|
def minimumTrailOpacity(self):
|
||||||
|
return self._minimumTrailOpacity
|
||||||
|
|
||||||
|
def trailFadePercentage(self):
|
||||||
|
return self._trailFadePercentage
|
||||||
|
|
||||||
|
def revolutionsPersSecond(self):
|
||||||
|
return self._revolutionsPerSecond
|
||||||
|
|
||||||
|
def numberOfLines(self):
|
||||||
|
return self._numberOfLines
|
||||||
|
|
||||||
|
def lineLength(self):
|
||||||
|
return self._lineLength
|
||||||
|
|
||||||
|
def lineWidth(self):
|
||||||
|
return self._lineWidth
|
||||||
|
|
||||||
|
def innerRadius(self):
|
||||||
|
return self._innerRadius
|
||||||
|
|
||||||
|
def setRoundness(self, roundness):
|
||||||
|
self._roundness = max(0.0, min(100.0, roundness))
|
||||||
|
|
||||||
|
def setColor(self, color=Qt.black):
|
||||||
|
self._color = QColor(color)
|
||||||
|
|
||||||
|
def setRevolutionsPerSecond(self, revolutionsPerSecond):
|
||||||
|
self._revolutionsPerSecond = revolutionsPerSecond
|
||||||
|
self.updateTimer()
|
||||||
|
|
||||||
|
def setTrailFadePercentage(self, trail):
|
||||||
|
self._trailFadePercentage = trail
|
||||||
|
|
||||||
|
def setMinimumTrailOpacity(self, minimumTrailOpacity):
|
||||||
|
self._minimumTrailOpacity = minimumTrailOpacity
|
||||||
|
|
||||||
|
def rotate(self):
|
||||||
|
self._currentCounter += 1
|
||||||
|
if self._currentCounter >= self._numberOfLines:
|
||||||
|
self._currentCounter = 0
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def updateSize(self):
|
||||||
|
self.size = (self._innerRadius + self._lineLength) * 2
|
||||||
|
self.setFixedSize(self.size, self.size)
|
||||||
|
|
||||||
|
def updateTimer(self):
|
||||||
|
self._timer.setInterval(1000 / (self._numberOfLines * self._revolutionsPerSecond))
|
||||||
|
|
||||||
|
def lineCountDistanceFromPrimary(self, current, primary, totalNrOfLines):
|
||||||
|
distance = primary - current
|
||||||
|
if distance < 0:
|
||||||
|
distance += totalNrOfLines
|
||||||
|
return distance
|
||||||
|
|
||||||
|
def currentLineColor(self, countDistance, totalNrOfLines, trailFadePerc, minOpacity, colorinput):
|
||||||
|
color = QColor(colorinput)
|
||||||
|
if countDistance == 0:
|
||||||
|
return color
|
||||||
|
minAlphaF = minOpacity / 100.0
|
||||||
|
distanceThreshold = int(math.ceil((totalNrOfLines - 1) * trailFadePerc / 100.0))
|
||||||
|
if countDistance > distanceThreshold:
|
||||||
|
color.setAlphaF(minAlphaF)
|
||||||
|
else:
|
||||||
|
alphaDiff = color.alphaF() - minAlphaF
|
||||||
|
gradient = alphaDiff / float(distanceThreshold + 1)
|
||||||
|
resultAlpha = color.alphaF() - gradient * countDistance
|
||||||
|
# If alpha is out of bounds, clip it.
|
||||||
|
resultAlpha = min(1.0, max(0.0, resultAlpha))
|
||||||
|
color.setAlphaF(resultAlpha)
|
||||||
|
return color
|
Loading…
Reference in New Issue