# -*- coding: utf-8 -*-
"""
lantz.ui.widgets
~~~~~~~~~~~~~~~~
Implements UI widgets based on Qt widgets. To achieve functionality,
instances of QtWidgets are patched.
:copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
import time
import json
import inspect
from lantz.utils import is_building_docs
from lantz.utils.qt import QtCore, QtGui
__PRINT_TRACEBACK__ = True
try:
from docutils import core as doc_core
except ImportError:
class doc_core(object):
@staticmethod
def publish_parts(rst, *args, **kwargs):
return rst
from .. import Q_, Driver, initialize_many
from ..feat import MISSING, DictFeat
from ..log import get_logger
QtGui.QToolTip.setFont(QtGui.QFont('SansSerif', 10))
logger = get_logger('lantz.ui', False)
def _rst_to_html(rst):
"""Convert rst docstring to HTML.
"""
parts = doc_core.publish_parts(rst, writer_name="html")
return parts['body']
def _params_doc(rst):
"""Extract
"""
if not rst:
return ''
docs = {}
rst = ' '.join(rst.splitlines())
key = None
for line in rst.split(':'):
line = line.strip()
if key:
docs[key] = line.strip()
key = None
else:
for prefix in ('param', 'parameter', 'arg', 'argument', 'key', 'keyword'):
if line.startswith(prefix):
key = line[len(prefix):].strip()
break
return docs
[docs]def register_wrapper(cls):
"""Register a class as lantz wrapper for QWidget subclasses.
The class must contain a field (_WRAPPERS) with a tuple of the
QWidget subclasses that it wraps.
"""
for wrapped in cls._WRAPPED:
if wrapped in cls._WRAPPERS:
logger.warn('{} is already registered to {}.'.format(wrapped, cls._WRAPPERS[wrapped]))
if is_building_docs:
cls._WRAPPERS[wrapped] = type(wrapped.__name__ + 'Wrapped',
(cls, ), {'_IS_LANTZ_WRAPPER': True})
else:
cls._WRAPPERS[wrapped] = type(wrapped.__name__ + 'Wrapped',
(cls, wrapped), {'_IS_LANTZ_WRAPPER': True})
return cls
@register_wrapper
class DictFeatWidget(QtGui.QWidget):
"""Widget to show a DictFeat.
:param parent: parent widget.
:param target: driver object to connect.
:param feat: DictFeat to connect.
"""
def __init__(self, parent, target, feat):
super().__init__(parent)
self._feat = feat
layout = QtGui.QHBoxLayout(self)
if feat.keys:
wid = QtGui.QComboBox()
if isinstance(feat.keys, dict):
self._keys = list(feat.keys.keys())
else:
self._keys = list(feat.keys)
wid.addItems([str(key) for key in self._keys])
wid.currentIndexChanged.connect(self._combobox_changed)
else:
wid = QtGui.QLineEdit()
wid.textChanged.connect(self._lineedit_changed)
layout.addWidget(wid)
self._key_widget = wid
wid = WidgetMixin.from_feat(feat)
wid.bind_feat(feat)
wid.feat_key = self._keys[0]
wid.lantz_target = target
layout.addWidget(wid)
self._value_widget = wid
@QtCore.Slot(int, object, object)
def _combobox_changed(self, value, old_value=MISSING, other=MISSING):
self._value_widget.feat_key = self._keys[self._key_widget.currentIndex()]
@QtCore.Slot(str, object, object)
def _lineedit_changed(self, value, old_value=MISSING, other=MISSING):
self._value_widget.feat_key = self._key_widget.text()
def value(self):
"""Get widget value.
"""
return self._value_widget.value()
def setValue(self, value):
"""Set widget value.
"""
if value is MISSING:
return
self._value_widget.setValue(value)
def setReadOnly(self, value):
"""Set read only s
"""
self._value_widget.setReadOnly(value)
@property
def lantz_target(self):
"""Driver connected to this widget.
"""
return self._value_widget._lantz_target
@lantz_target.setter
def lantz_target(self, driver):
self._value_widget._lantz_target = driver
@property
def readable(self):
"""If the Feat associated with the widget can be read (get).
"""
return self._value_widget.readable
@property
def writable(self):
"""If the Feat associated with the widget can be written (set).
"""
return self._value_widget.writable
def value_from_feat(self):
return self._value_widget.value_from_feat()
class LabeledFeatWidget(QtGui.QWidget):
"""Widget containing a label, a control, and a get a set button.
:param parent: parent widget.
:param target: driver object to connect.
:param feat: Feat to connect.
"""
def __init__(self, parent, target, feat):
super().__init__(parent)
layout = QtGui.QHBoxLayout(self)
self._label = QtGui.QLabel()
self._label.setText(feat.name)
self._label.setFixedWidth(120)
self._label.setToolTip(_rst_to_html(feat.__doc__))
layout.addWidget(self._label)
if isinstance(feat.feat, DictFeat):
self._widget = DictFeatWidget(parent, target, feat)
else:
self._widget = WidgetMixin.from_feat(feat)
self._widget.bind_feat(feat)
self._widget.lantz_target = target
layout.addWidget(self._widget)
self._get = QtGui.QPushButton()
self._get.setText('get')
self._get.setEnabled(self._widget.readable)
self._get.setFixedWidth(60)
layout.addWidget(self._get)
self._set = QtGui.QPushButton()
self._set.setText('set')
self._set.setEnabled(self._widget.writable)
self._set.setFixedWidth(60)
layout.addWidget(self._set)
self._get.clicked.connect(self.on_get_clicked)
self._set.clicked.connect(self.on_set_clicked)
self._widget._update_on_change = self._widget.writable
self.widgets = (self._label, self._widget, self._get, self._set)
@property
def label_width(self):
"""Width of the label
"""
return self._label.width
@label_width.setter
def label_width(self, value):
self._label.setFixedWidth(value)
@property
def lantz_target(self):
"""Driver connected to this widget.
"""
return self._widget._lantz_target
@lantz_target.setter
def lantz_target(self, driver):
self._widget._lantz_target = driver
@QtCore.Slot()
def on_get_clicked(self):
self._widget.value_from_feat()
@QtCore.Slot()
def on_set_clicked(self):
font = QtGui.QFont()
font.setItalic(False)
self._widget.setFont(font)
self._widget.value_to_feat()
@property
def readable(self):
"""If the Feat associated with the widget can be read (get).
"""
return self._widget.readable
@property
def writable(self):
"""If the Feat associated with the widget can be written (set).
"""
return self._widget.writable
class DriverTestWidget(QtGui.QWidget):
"""Widget that is automatically filled to control all Feats of a given driver.
:param parent: parent widget.
:param target: driver object to map.
"""
def __init__(self, parent, target):
super().__init__(parent)
self._lantz_target = target
layout = QtGui.QVBoxLayout(self)
label = QtGui.QLabel()
label.setText(str(target))
layout.addWidget(label)
recall = QtGui.QPushButton()
recall.setText('Refresh')
recall.clicked.connect(lambda x: target.refresh())
update = QtGui.QPushButton()
update.setText('Update')
update.clicked.connect(lambda x: target.update(self.widgets_values_as_dict()))
auto = QtGui.QCheckBox()
auto.setText('Update on change')
auto.setChecked(True)
auto.stateChanged.connect(self.update_on_change)
hlayout = QtGui.QHBoxLayout()
hlayout.addWidget(recall)
hlayout.addWidget(update)
hlayout.addWidget(auto)
layout.addLayout(hlayout)
self.writable_widgets = []
self.widgets = []
# Feat
for feat_name, feat in sorted(target.feats.items()):
try:
feat_widget = LabeledFeatWidget(self, target, feat)
self.widgets.append(feat_widget)
if feat_widget.writable:
self.writable_widgets.append(feat_widget)
layout.addWidget(feat_widget)
except Exception as ex:
logger.debug('Could not create control for {}: {}'.format(feat_name, ex))
if __PRINT_TRACEBACK__:
import traceback
traceback.print_exc()
# Actions
line = QtGui.QFrame(self)
#self.line.setGeometry(QtCore.QRect(110, 80, 351, 31))
line.setFrameShape(QtGui.QFrame.HLine)
line.setFrameShadow(QtGui.QFrame.Sunken)
layout.addWidget(line)
actions_label = QtGui.QLabel(self)
actions_label.setText('Actions:')
actions_label.setFixedWidth(120)
self.actions_combo = QtGui.QComboBox(self)
self.actions_combo.addItems(list(target.actions.keys()))
actions_button = QtGui.QPushButton(self)
actions_button.setFixedWidth(60)
actions_button.setText('Run')
actions_button.clicked.connect(self.on_run_clicked)
alayout = QtGui.QHBoxLayout()
alayout.addWidget(actions_label)
alayout.addWidget(self.actions_combo)
alayout.addWidget(actions_button)
layout.addLayout(alayout)
@QtCore.Slot()
def on_run_clicked(self):
ArgumentsInputDialog.run(getattr(self._lantz_target, self.actions_combo.currentText()), self)
def update_on_change(self, new_state):
"""Set the 'update_on_change' flag to new_state in each writable widget
within this widget. If True, the driver will be updated after each change.
"""
for widget in self.writable_widgets:
widget._widget._update_on_change = new_state
def widgets_values_as_dict(self):
"""Return a dictionary mapping each writable feat name to the current
value of the widget.
"""
return {widget._feat.name: widget._widget.value()
for widget in self.writable_widgets}
@property
def lantz_target(self):
"""Driver connected to this widget.
"""
return self._lantz_target
@lantz_target.setter
def lantz_target(self, driver):
self._lantz_target = driver
for widget in self.widgets:
widget.lantz_target = driver
class SetupTestWidget(QtGui.QWidget):
"""Widget to control multiple drivers.
:param parent: parent widget.
:param targets: iterable of driver object to map.
"""
def __init__(self, parent, targets):
super().__init__(parent)
layout = QtGui.QHBoxLayout(self)
tab_widget = QtGui.QTabWidget(self)
tab_widget.setTabsClosable(False)
for target in targets:
widget = DriverTestWidget(parent, target)
tab_widget.addTab(widget, target.name)
layout.addWidget(tab_widget)
[docs]def connect_feat(widget, target, feat_name=None, feat_key=MISSING):
"""Connect a feature from a given driver to a widget. Calling this
function also patches the widget is necessary.
If applied two times with the same widget, it will connect to the target
provided in the second call. This behaviour can be useful to change the
connection target without rebuilding the whole UI. Alternative, after
connect has been called the first time, widget will have a property
`lantz_target` that can be used to achieve the same thing.
:param widget: widget instance.
:param target: driver instance.
:param feat_name: feature name. If None, connect using widget name.
:param feat_key: For a DictFeat, this defines which key to show.
"""
logger.debug('Connecting {} to {}, {}, {}'.format(widget, target, feat_name, feat_key))
if not isinstance(target, Driver):
raise TypeError('Connect target must be an instance of lantz.Driver, not {}'.format(target))
if not feat_name:
feat_name = widget.objectName()
#: Reconnect
if hasattr(widget, '_feat.name') and widget._feat.name == feat_name:
widget.lantz_target = target
return
feat = target.feats[feat_name]
WidgetMixin.wrap(widget)
widget.bind_feat(feat)
widget.feat_key = feat_key
widget.lantz_target = target
[docs]def connect_driver(parent, target, *, prefix='', sep='__'):
"""Connect all children widgets to their corresponding lantz feature
matching by name. Non-matching names are ignored.
:param parent: parent widget.
:param target: the driver.
:param prefix: prefix to be prepended to the lantz feature (default = '')
:param sep: separator between prefix, name and suffix
"""
logger.debug('Connecting {} to {}, {}, {}'.format(parent, target, prefix, sep))
ChildrenWidgets.patch(parent)
if prefix:
prefix += sep
for name, _, wid in parent.widgets:
if prefix and name.startswith(prefix):
name = name[len(prefix):]
if sep in name:
name, _ = name.split(sep, 1)
if name in target.feats:
connect_feat(wid, target, name)
[docs]def connect_setup(parent, targets, *, prefix=None, sep='__'):
"""Connect all children widget to their corresponding
:param parent: parent widget.
:param targets: iterable of drivers.
:param prefix: prefix to be prepended to the lantz feature name
if None, the driver name will be used (default)
if it is a dict, the driver name will be used to obtain
he prefix.
"""
logger.debug('Connecting {} to {}, {}, {}'.format(parent, targets, prefix, sep))
ChildrenWidgets.patch(parent)
for target in targets:
name = target.name
if isinstance(prefix, dict):
name = prefix[name]
connect_driver(parent, target, prefix=name, sep=sep)
[docs]def request_new_units(current_units):
"""Ask for new units using a dialog box and return them.
:param current_units: current units or magnitude.
:type current_units: Quantity
"""
new_units = UnitInputDialog.get_units(current_units)
if new_units is None:
return None
try:
return Q_(1, new_units)
except LookupError:
# cannot parse units
return None
@register_wrapper
class MagnitudeMixin(WidgetMixin):
_WRAPPED = (QtGui.QDoubleSpinBox, )
def keyPressEvent(self, event):
super().keyPressEvent(event)
if self._units and event.text() == 'u':
self.change_units(request_new_units(self.value()))
def bind_feat(self, feat):
super().bind_feat(feat)
#: self._units are the current units displayed by the widget.
#: Respects units declared in the suffix
if feat.units:
suf = (self.suffix() if hasattr(self, 'suffix') else feat.units) or feat.units
self._units = Q_(1, suf)
self.change_units(self._units)
else:
self._units = None
if feat.limits:
self.change_limits(None)
def change_units(self, new_units):
"""Update displayed suffix and stored units.
"""
if new_units is None:
return
try:
rescaled = self.value().to(new_units)
except ValueError:
# incompatible units
return None
else:
if hasattr(self, 'setSuffix'):
self.setSuffix(' ' + str(new_units.units))
self.change_limits(new_units)
self._units = new_units
self.setValue(rescaled)
def change_limits(self, new_units):
"""Change the limits (range) of the control taking the original values
from the feat and scaling them to the new_units.
"""
if not hasattr(self, 'setRange'):
return
rng = self._feat.limits or (float('-inf'), float('+inf'))
if new_units:
conv = lambda ndx: Q_(rng[ndx], self._feat.units).to(new_units).magnitude
else:
conv = lambda ndx: rng[ndx]
if len(rng) == 1:
self.setRange(0, conv(0))
else:
self.setRange(conv(0), conv(1))
if len(rng) == 3:
self.setSingleStep(conv(2))
def value(self):
"""Get widget value and scale by units.
"""
if self._units:
return super().value() * self._units
return super().value()
def setValue(self, value):
"""Set widget value scaled by units.
"""
if value is MISSING:
font = QtGui.QFont()
font.setItalic(True)
self.setFont(font)
elif isinstance(value, Q_):
super().setValue(value.to(self._units).magnitude)
else:
super().setValue(value)
@register_wrapper
class SliderMixin(MagnitudeMixin):
_WRAPPED = (QtGui.QSlider, QtGui.QDial, QtGui.QProgressBar, QtGui.QScrollBar)
def setReadOnly(self, value):
super().setEnabled(not value)
@register_wrapper
class LCDNumberMixin(MagnitudeMixin):
_WRAPPED = (QtGui.QLCDNumber, )
@classmethod
def _wrap(cls, widget):
super()._wrap(widget)
#TODO: Create a real valueChanged Signal.
widget.valueChanged = widget.overflow
def setReadOnly(self, value):
super().setEnabled(not value)
def setValue(self, value):
if value is MISSING:
font = QtGui.QFont()
font.setItalic(True)
self.setFont(font)
return
elif isinstance(value, Q_):
super().display(value.to(self._units).magnitude)
else:
super().display(value)
def value(self):
return super().value()
@register_wrapper
class QComboBoxMixin(WidgetMixin):
_WRAPPED = (QtGui.QComboBox, )
@classmethod
def _wrap(cls, widget):
super()._wrap(widget)
widget.valueChanged = widget.currentIndexChanged
def value(self):
return self.currentText()
def setValue(self, value):
if value is MISSING:
font = QtGui.QFont()
font.setItalic(True)
self.setFont(font)
return
self.setCurrentIndex(self.__values.index(value))
def setReadOnly(self, value):
self.setEnabled(not value)
def bind_feat(self, feat):
super().bind_feat(feat)
if isinstance(self._feat.values, dict):
self.__values = list(self._feat.values.keys())
else:
self.__values = list(self.__values)
self.clear()
self.addItems([str(value) for value in self.__values])
@register_wrapper
class QCheckBoxMixin(WidgetMixin):
_WRAPPED = (QtGui.QCheckBox, )
@classmethod
def _wrap(cls, widget):
super()._wrap(widget)
widget.valueChanged = widget.stateChanged
def setReadOnly(self, value):
self.setCheckable(not value)
def value(self):
return self.isChecked()
def setValue(self, value):
if value is MISSING:
return
self.setChecked(value)
@register_wrapper
class QLineEditMixin(WidgetMixin):
_WRAPPED = (QtGui.QLineEdit, )
@classmethod
def _wrap(cls, widget):
super()._wrap(widget)
widget.valueChanged = widget.textChanged
def value(self):
return self.text()
def setValue(self, value):
if value is MISSING:
return
return self.setText(value)
class ArgumentsInputDialog(QtGui.QDialog):
def __init__(self, argspec, parent=None, window_title='Function arguments', doc=None):
super().__init__(parent)
vlayout = QtGui.QVBoxLayout(self)
layout = QtGui.QFormLayout()
widgets = []
defaults = argspec.defaults if argspec.defaults else ()
defaults = ('', ) * (len(argspec.args[1:]) - len(defaults)) + defaults
self.arguments = {}
for arg, default in zip(argspec.args[1:], defaults):
wid = QtGui.QLineEdit(self)
wid.setObjectName(arg)
wid.setText(json.dumps(default))
self.arguments[arg] = default
layout.addRow(arg, wid)
widgets.append(wid)
wid.textChanged.connect(self.on_widget_change(wid))
if doc and arg in doc:
wid.setToolTip(doc[arg])
self.widgets = widgets
buttonBox = QtGui.QDialogButtonBox()
buttonBox.setOrientation(QtCore.Qt.Horizontal)
buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Ok)
buttonBox.setEnabled(True)
buttonBox.accepted.connect(self.accept)
vlayout.addLayout(layout)
label = QtGui.QLabel()
label.setText('Values are decoded from text using as JSON.')
vlayout.addWidget(label)
vlayout.addWidget(buttonBox)
self.buttonBox = buttonBox
self.valid = {wid.objectName(): True for wid in self.widgets}
self.setWindowTitle(window_title)
def on_widget_change(self, widget):
name = widget.objectName()
def validate(value):
try:
if value:
value = json.loads(value)
else:
value = None
palette = QtGui.QPalette()
palette.setColor(widget.backgroundRole(), QtGui.QColor('white'))
widget.setPalette(palette)
self.arguments[name] = value
self.valid[name] = True
except:
palette = QtGui.QPalette()
palette.setColor(widget.backgroundRole(), QtGui.QColor(255, 102, 102))
widget.setPalette(palette)
self.valid[name] = False
self.buttonBox.setEnabled(all(self.valid.values()))
return validate
def accept(self):
super().accept()
@staticmethod
def run(func, parent=None):
"""Creates and display a UnitInputDialog and return new units.
Return None if the user cancelled.
"""
wrapped = getattr(func, '__wrapped__', func)
name = wrapped.__name__
doc = wrapped.__doc__
argspec = inspect.getargspec(wrapped)
arguments = {}
if len(argspec.args) > 1:
dialog = ArgumentsInputDialog(argspec, parent,
window_title=name+ ' arguments',
doc=_params_doc(doc))
if not dialog.exec_():
return None
arguments = dialog.arguments
try:
func(**arguments)
except Exception as e:
logger.exception(e)
QtGui.QMessageBox.critical(parent, 'Lantz',
'Instrument error while calling {}'.format(name),
QtGui.QMessageBox.Ok,
QtGui.QMessageBox.NoButton)
class UnitInputDialog(QtGui.QDialog):
"""Dialog to select new units. Checks compatibility while typing
and does not allow to continue if incompatible.
Returns None if cancelled.
:param units: current units.
:param parent: parent widget.
>>> new_units = UnitInputDialog.get_units('ms')
"""
def __init__(self, units, parent=None):
super().__init__(parent)
self.setupUi(parent)
self.units = units
self.source_units.setText(str(units))
def setupUi(self, parent):
self.resize(275, 172)
self.setWindowTitle('Convert units')
self.layout = QtGui.QVBoxLayout(parent)
self.layout.setSizeConstraint(QtGui.QLayout.SetFixedSize)
align = (QtCore.Qt.AlignRight | QtCore.Qt.AlignTrailing | QtCore.Qt.AlignVCenter)
self.layout1 = QtGui.QHBoxLayout()
self.label1 = QtGui.QLabel()
self.label1.setMinimumSize(QtCore.QSize(100, 0))
self.label1.setText('Convert from:')
self.label1.setAlignment(align)
self.layout1.addWidget(self.label1)
self.source_units = QtGui.QLineEdit()
self.source_units.setReadOnly(True)
self.layout1.addWidget(self.source_units)
self.layout.addLayout(self.layout1)
self.layout2 = QtGui.QHBoxLayout()
self.label2 = QtGui.QLabel()
self.label2.setMinimumSize(QtCore.QSize(100, 0))
self.label2.setText('to:')
self.label2.setAlignment(align)
self.layout2.addWidget(self.label2)
self.destination_units = QtGui.QLineEdit()
self.layout2.addWidget(self.destination_units)
self.layout.addLayout(self.layout2)
self.message = QtGui.QLabel()
self.message.setText('')
self.message.setAlignment(QtCore.Qt.AlignCenter)
self.layout.addWidget(self.message)
self.buttonBox = QtGui.QDialogButtonBox()
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Ok)
self.layout.addWidget(self.buttonBox)
self.buttonBox.setEnabled(False)
self.buttonBox.accepted.connect(self.accept)
self.destination_units.textChanged.connect(self.check)
self.setLayout(self.layout)
self.destination_units.setFocus()
def check(self):
units = self.destination_units.text().strip()
if not units:
return
try:
new_units = Q_(1, units)
factor = self.units.to(new_units).magnitude
except LookupError or SyntaxError:
self.message.setText('Cannot parse units')
self.buttonBox.setEnabled(False)
except ValueError:
self.message.setText('Incompatible units')
self.buttonBox.setEnabled(False)
else:
self.message.setText('factor {:f}'.format(factor))
self.buttonBox.setEnabled(True)
@staticmethod
def get_units(units):
"""Creates and display a UnitInputDialog and return new units.
Return None if the user cancelled.
"""
dialog = UnitInputDialog(Q_(1, units.units))
if dialog.exec_():
return dialog.destination_units.text()
return None
class InitializerHelper(QtCore.QObject):
initializing = QtCore.Signal(object)
initialized = QtCore.Signal(object)
exception = QtCore.Signal(object, object)
finished = QtCore.Signal(float)
def __init__(self, drivers, register_finalizer, parallel, dependencies):
super().__init__()
self.drivers = drivers
self.register_finalizer = register_finalizer
self.parallel = parallel
self.dependencies = dependencies
def process(self):
start = time.time()
initialize_many(drivers=self.drivers, register_finalizer=self.register_finalizer,
on_initializing=self.on_initializing,
on_initialized=self.on_initialized,
on_exception=self.on_exception,
concurrent=self.parallel,
dependencies=self.dependencies)
self.finished.emit(time.time() - start)
def on_initializing(self, driver):
self.initializing.emit(driver)
def on_initialized(self, driver):
self.initialized.emit(driver)
def on_exception(self, driver, ex):
self.exception.emit(driver, ex)
[docs]def initialize_and_report(widget, drivers, register_finalizer=True,
initializing_msg='Initializing ...', initialized_msg='Initialized',
concurrent=True, dependencies=None):
"""Initialize drivers while reporting the status in a QtWidget.
:param widget: Qt Widget where the status information is going to be shown.
:param drivers: iterable of drivers to initialize.
:param register_finalizer: register driver.finalize method to be called at python exit.
:param initializing_msg: message to be displayed while initializing.
:param initialized_msg: message to be displayed after successful initialization.
:param concurrent: indicates that drivers with satisfied dependencies
should be initialized concurrently.
:param dependencies: indicates which drivers depend on others to be initialized.
each key is a driver name, and the corresponding
value is an iterable with its dependencies.
:return: the QThread doing the initialization.
"""
timing = {}
thread = QtCore.QThread()
helper = InitializerHelper(drivers, register_finalizer, concurrent, dependencies)
helper.moveToThread(thread)
thread.helper = helper
if isinstance(widget, QtGui.QTableWidget):
def _initializing(driver):
timing[driver] = time.time()
row = drivers.index(driver)
widget.setItem(row, 2, QtGui.QTableWidgetItem(initializing_msg))
def _initialized(driver):
delta = time.time() - timing[driver]
row = drivers.index(driver)
widget.setItem(row, 2, QtGui.QTableWidgetItem(initialized_msg + ' ({:.1f} sec)'.format(delta)))
def _exception(driver, e):
delta = time.time() - timing[driver]
row = drivers.index(driver)
widget.setItem(row, 2, QtGui.QTableWidgetItem('{} ({:.1f} sec)'.format(e, delta)))
def _done(duration):
widget.setItem(len(drivers), 2, QtGui.QTableWidgetItem('{:.1f} sec'.format(duration)))
thread.quit()
widget.clearContents()
widget.setRowCount(len(drivers) + 1)
for row, driver in enumerate(drivers):
widget.setItem(row, 0, QtGui.QTableWidgetItem(driver.name))
widget.setItem(row, 1, QtGui.QTableWidgetItem(driver.__class__.__name__))
widget.setItem(row, 2, QtGui.QTableWidgetItem(''))
widget.resizeColumnToContents(0)
widget.horizontalHeader().setStretchLastSection(True)
elif isinstance(widget, QtGui.QLineEdit):
def _initializing(driver):
timing[driver] = time.time()
widget.setText('{} ({}) > {}'.format(driver.name, driver.__class__.__name__,
initializing_msg))
def _initialized(driver):
delta = time.time() - timing[driver]
widget.setText('{} ({}) > {} ({:.1f} sec)'.format(driver.name, driver.__class__.__name__,
initialized_msg, delta))
def _exception(driver, e):
delta = time.time() - timing[driver]
widget.setText('{} ({}) > {} ({:.1f} sec)'.format(driver.name, driver.__class__.__name__,
e, delta))
def _done(duration):
widget.setText('Initialized in {:.1f} sec'.format(duration))
thread.quit()
widget.setReadOnly(True)
elif isinstance(widget, QtGui.QTextEdit):
def _initializing(driver):
timing[driver] = time.time()
widget.append('{} ({}) > {}'.format(driver.name, driver.__class__.__name__,
initializing_msg))
def _initialized(driver):
delta = time.time() - timing[driver]
widget.append('{} ({}) > {} ({:.1f} sec)'.format(driver.name, driver.__class__.__name__,
initialized_msg, delta))
def _exception(driver, e):
delta = time.time() - timing[driver]
widget.append('{} ({}) > {} ({:.1f} sec)'.format(driver.name, driver.__class__.__name__,
e, delta))
def _done(duration):
widget.append('Initialized in {:.1f} sec'.format(duration))
thread.quit()
widget.setReadOnly(True)
else:
raise TypeError('Unknown widget type {}.'.format(type(widget)))
thread.started.connect(helper.process)
helper.initializing.connect(_initializing)
helper.initialized.connect(_initialized)
helper.exception.connect(_exception)
helper.finished.connect(_done)
thread.start()
return thread