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
|
||||
|
||||
sub_clients = dict()
|
||||
for notifier_name, module in (("explist", explorer),
|
||||
("datasets", datasets),
|
||||
("schedule", schedule),
|
||||
("log", log)):
|
||||
subscriber = ModelSubscriber(notifier_name, module.Model)
|
||||
for notifier_name, modelf in (("explist", explorer.Model),
|
||||
("explist_status", explorer.StatusUpdater),
|
||||
("datasets", datasets.Model),
|
||||
("schedule", schedule.Model),
|
||||
("log", log.Model)):
|
||||
subscriber = ModelSubscriber(notifier_name, modelf)
|
||||
loop.run_until_complete(subscriber.connect(
|
||||
args.server, args.port_notify))
|
||||
atexit_register_coroutine(subscriber.close)
|
||||
@ -123,6 +124,7 @@ def main():
|
||||
smgr.register(d_shortcuts)
|
||||
d_explorer = explorer.ExplorerDock(status_bar, expmgr, d_shortcuts,
|
||||
sub_clients["explist"],
|
||||
sub_clients["explist_status"],
|
||||
rpc_clients["schedule"],
|
||||
rpc_clients["experiment_db"])
|
||||
|
||||
|
@ -7,6 +7,7 @@ from PyQt5 import QtCore, QtWidgets
|
||||
|
||||
from artiq.gui.tools import LayoutWidget
|
||||
from artiq.gui.models import DictSyncTreeSepModel
|
||||
from artiq.gui.waitingspinnerwidget import QtWaitingSpinner
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -117,38 +118,81 @@ class Model(DictSyncTreeSepModel):
|
||||
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):
|
||||
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")
|
||||
self.setObjectName("Explorer")
|
||||
self.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable |
|
||||
QtWidgets.QDockWidget.DockWidgetFloatable)
|
||||
|
||||
layout = QtWidgets.QGridLayout()
|
||||
top_widget = QtWidgets.QWidget()
|
||||
top_widget.setLayout(layout)
|
||||
top_widget = LayoutWidget()
|
||||
self.setWidget(top_widget)
|
||||
layout.setSpacing(5)
|
||||
layout.setContentsMargins(5, 5, 5, 5)
|
||||
|
||||
self.status_bar = status_bar
|
||||
self.exp_manager = exp_manager
|
||||
self.d_shortcuts = d_shortcuts
|
||||
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.setHeaderHidden(True)
|
||||
self.el.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectItems)
|
||||
layout.addWidget(self.el, 0, 0, 1, 2)
|
||||
self.el.doubleClicked.connect(
|
||||
partial(self.expname_action, "open_experiment"))
|
||||
self.el_buttons.addWidget(self.el, 0, 0, colspan=2)
|
||||
|
||||
open = QtWidgets.QPushButton("Open")
|
||||
open.setIcon(QtWidgets.QApplication.style().standardIcon(
|
||||
QtWidgets.QStyle.SP_DialogOpenButton))
|
||||
open.setToolTip("Open the selected experiment (Return)")
|
||||
layout.addWidget(open, 1, 0)
|
||||
self.el_buttons.addWidget(open, 1, 0)
|
||||
open.clicked.connect(
|
||||
partial(self.expname_action, "open_experiment"))
|
||||
|
||||
@ -156,7 +200,7 @@ class ExplorerDock(QtWidgets.QDockWidget):
|
||||
submit.setIcon(QtWidgets.QApplication.style().standardIcon(
|
||||
QtWidgets.QStyle.SP_DialogOkButton))
|
||||
submit.setToolTip("Schedule the selected experiment (Ctrl+Return)")
|
||||
layout.addWidget(submit, 1, 1)
|
||||
self.el_buttons.addWidget(submit, 1, 1)
|
||||
submit.clicked.connect(
|
||||
partial(self.expname_action, "submit"))
|
||||
|
||||
@ -201,7 +245,6 @@ class ExplorerDock(QtWidgets.QDockWidget):
|
||||
self.el)
|
||||
def scan_repository():
|
||||
asyncio.ensure_future(experiment_db_ctl.scan_repository_async())
|
||||
self.status_bar.showMessage("Requested repository scan")
|
||||
scan_repository_action.triggered.connect(scan_repository)
|
||||
self.el.addAction(scan_repository_action)
|
||||
|
||||
@ -213,6 +256,11 @@ class ExplorerDock(QtWidgets.QDockWidget):
|
||||
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))
|
||||
|
||||
def set_model(self, model):
|
||||
self.explist_model = model
|
||||
self.el.setModel(model)
|
||||
@ -237,3 +285,14 @@ class ExplorerDock(QtWidgets.QDockWidget):
|
||||
self.d_shortcuts.set_shortcut(nr, expurl)
|
||||
self.status_bar.showMessage("Set shortcut F{} to '{}'"
|
||||
.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)
|
||||
|
186
artiq/gui/waitingspinnerwidget.py
Normal file
186
artiq/gui/waitingspinnerwidget.py
Normal file
@ -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
Block a user