class MultiWidget(QWidget): valueModified = pyqtSignal() valueChosen = pyqtSignal(object) def __init__(self, parent, types, curvalue, client, allow_enter=False, valinfo=None): QWidget.__init__(self, parent) layout = self._layout = QGridLayout() layout.setContentsMargins(0, 0, 0, 0) self._widgets = [] if valinfo is not None: for i, info in enumerate(valinfo): layout.addWidget(QLabel(info.name), 0, i) for i, (typ, val) in enumerate(zip(types, curvalue)): widget = create(self, typ, val, client=client, allow_enter=allow_enter) self._widgets.append(widget) widget.valueModified.connect(self.valueModified) if allow_enter: widget.valueChosen.connect( lambda val: self.valueChosen.emit(self.getValue())) layout.addWidget(widget, 1, i) self.setLayout(layout) def getValue(self): return tuple(w.getValue() for w in self._widgets)
class SetOfWidget(QWidget): valueModified = pyqtSignal() valueChosen = pyqtSignal(object) def __init__(self, parent, values, curvalue, client): QWidget.__init__(self, parent) layout = self._layout = QVBoxLayout() self.checkboxes = [] self.values = [] curvalue = curvalue or set() for value in values: checkbox = QCheckBox(str(value), self) if value in curvalue: checkbox.setCheckState(Qt.Checked) checkbox.stateChanged.connect(self.on_checkbox_stateChanged) layout.addWidget(checkbox) self.checkboxes.append(checkbox) layout.setContentsMargins(0, 0, 0, 0) self.setLayout(layout) def on_checkbox_stateChanged(self, state): self.valueModified.emit() def getValue(self): result = set() for value, checkbox in zip(self.values, self.checkboxes): if checkbox.isChecked(): result.add(value) return result
class DictWithWidget(QWidget): valueModified = pyqtSignal() valueChosen = pyqtSignal(object) def __init__(self, parent, keys, types, curvalue, client, allow_enter=False): QWidget.__init__(self, parent) layout = self._layout = QFormLayout() layout.setContentsMargins(0, 0, 0, 0) self.keys = keys self._widgets = [] for (key, typ, val) in zip(keys, types, curvalue.values()): widget = create(self, typ, val, client=client, allow_enter=allow_enter) self._widgets.append(widget) widget.valueModified.connect(self.valueModified) if allow_enter: widget.valueChosen.connect( lambda val: self.valueChosen.emit(self.getValue())) layout.addRow(key, widget) self.setLayout(layout) def getValue(self): return {k: w.getValue() for k, w in zip(self.keys, self._widgets)}
class PositionTable(QTableWidget): """Display all selected measurement positions. Each value is modifiable and removable. """ pointRemove = pyqtSignal(int) valueChanged = pyqtSignal(int, int, float) def __init__(self, parent=None): QTableWidget.__init__(self, parent) def mousePressEvent(self, event): if event.button() == Qt.RightButton: item = self.itemAt(event.pos()) if item: if QMessageBox.question(self, 'Remove data point', 'Do you ' 'really want to remove this data ' 'point?') == QMessageBox.Yes: event.accept() r = item.row() self.removeRow(r) self.pointRemove.emit(r) QTableWidget.mousePressEvent(self, event) def clearTable(self): while self.rowCount(): self.removeRow(0) self.pointRemove.emit(0) def getValues(self, row): return [float(self.item(row, i).text()) for i in range(3)]
class CheckWidget(QWidget): valueModified = pyqtSignal() valueChosen = pyqtSignal(object) def __init__(self, parent, inner, curvalue, client): QWidget.__init__(self, parent) layout = self._layout = QHBoxLayout() self.checkbox = QCheckBox(self) if curvalue is not None: self.checkbox.setCheckState(Qt.Checked) if curvalue is None: curvalue = inner() # generate a dummy value self.inner_widget = create(self, inner, curvalue, client=client) self.inner_widget.setEnabled(self.checkbox.isChecked()) self.inner_widget.valueModified.connect(self.valueModified) layout.addWidget(self.checkbox) layout.addWidget(self.inner_widget) layout.setContentsMargins(0, 0, 0, 0) self.checkbox.stateChanged.connect(self.on_checkbox_stateChanged) self.setLayout(layout) def on_checkbox_stateChanged(self, state): self.inner_widget.setEnabled(state == Qt.Checked) self.valueModified.emit() def getValue(self): if self.checkbox.isChecked(): return self.inner_widget.getValue() return None
class DeviceComboWidget(QComboBox): valueModified = pyqtSignal() valueChosen = pyqtSignal(object) def __init__(self, parent, curvalue, client, needs_class='nicos.core.device.Device', allow_enter=False): QComboBox.__init__(self, parent, editable=True) self.setSizePolicy( QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)) if client: devs = client.getDeviceList(needs_class) self.addItems(devs) try: index = devs.index(curvalue) self.setCurrentIndex(index) except ValueError: self.setEditText(curvalue) else: self.setEditText(curvalue) self.editTextChanged.connect(lambda _: self.valueModified.emit()) if allow_enter: self.lineEdit().returnPressed.connect( lambda: self.valueChosen.emit(self.currentText())) def getValue(self): return self.currentText()
class OneofdictOrWidget(QWidget): valueModified = pyqtSignal() valueChosen = pyqtSignal(object) def __init__(self, parent, inner, selector, buttons): QWidget.__init__(self, parent) self._inner = inner self._selector = selector layout = self._layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) inner.valueModified.connect(self.valueModified) inner.valueChosen.connect(self.valueChosen) if not buttons: selector.valueModified.connect(self.on_selector_valueModified) inner.setVisible(selector.getValue() is Ellipsis) else: selector.valueChosen.connect(self.valueChosen) layout.addWidget(selector) layout.addWidget(inner) self.setLayout(layout) def on_selector_valueModified(self): val = self._selector.getValue() self._inner.setVisible(val is Ellipsis) self.valueModified.emit() def getValue(self): val = self._selector.getValue() if val is Ellipsis: return self._inner.getValue() return val
class TableWidget(QTableWidget): valueModified = pyqtSignal() valueChosen = pyqtSignal(object) def __init__(self, parent, validator, curvalue, fmtstr, client, allow_enter=False): self._rows, self._cols = curvalue.shape self.validator = validator QTableWidget.__init__(self, self._rows, self._cols, parent) for i in range(self._rows): for j in range(self._cols): self.setItem(i, j, QTableWidgetItem(fmtstr % curvalue[i, j])) self.cellChanged.connect(lambda i, j: self.valueModified.emit()) def getValue(self): value = np.zeros((self._rows, self._cols)) for i in range(self._rows): for j in range(self._cols): value[i, j] = self.validator(self.item(i, j).text()) return value
class CacheSignals(QObject): """This must be a separate object since the metaclasses of BaseCacheClient and CacheSignals are not compatible. """ connected = pyqtSignal() disconnected = pyqtSignal() keyUpdated = pyqtSignal(str, object)
class MissingWidget(QLabel): valueModified = pyqtSignal() valueChosen = pyqtSignal(object) def __init__(self, parent, curvalue, text='editing impossible'): QLabel.__init__(self, parent) self.setText('(%s)' % text) self._value = curvalue def getValue(self): return self._value
class ExprWidget(QLineEdit): valueModified = pyqtSignal() valueChosen = pyqtSignal(object) def __init__(self, parent, curvalue, allow_enter=False): QLineEdit.__init__(self, parent) self.setText(cache_dump(curvalue)) self.textChanged.connect(lambda txt: self.valueModified.emit()) if allow_enter: self.returnPressed.connect( lambda: self.valueChosen.emit(cache_load(self.text()))) def getValue(self): return cache_load(self.text())
class PnPSetupQuestion(QMessageBox): """Special QMessageBox for asking what to do a new setup was detected.""" closed = pyqtSignal('QMessageBox') def __init__(self, parent, client, data): self.client = client client.setup.connect(self.on_client_setup) self.data = data add_mode = data[0] == 'added' if add_mode: message = ( '<b>New sample environment detected</b><br/>' 'A new sample environment <b>%s</b> has been detected:<br/>%s' % (data[1], data[2] or '')) else: message = ( '<b>Sample environment removed</b><br/>' 'The sample environment <b>%s</b> has been removed:<br/>%s' % (data[1], data[2] or '')) QMessageBox.__init__(self, QMessageBox.Information, 'NICOS Plug & Play', message, QMessageBox.NoButton, parent) self.setWindowModality(Qt.NonModal) self.b0 = self.addButton('Ignore', QMessageBox.RejectRole) self.b0.setIcon(self.style().standardIcon( QStyle.SP_DialogCancelButton)) if add_mode: self.b1 = self.addButton('Load setup', QMessageBox.YesRole) else: self.b1 = self.addButton('Remove setup', QMessageBox.YesRole) self.b1.setIcon(self.style().standardIcon(QStyle.SP_DialogOkButton)) self.b0.clicked.connect(self.on_ignore_clicked) self.b1.clicked.connect(self.on_execute_clicked) self.b0.setFocus() def on_ignore_clicked(self): self.closed.emit(self) self.reject() def on_execute_clicked(self): if self.data[0] == 'added': self.client.run('AddSetup(%r)' % self.data[1]) else: self.client.run('RemoveSetup(%r)' % self.data[1]) self.closed.emit(self) self.accept() def closeEvent(self, event): self.closed.emit(self) return QMessageBox.closeEvent(self, event) def on_client_setup(self, data): setupnames = data[0] if self.data[0] == 'added' and self.data[1] in setupnames: # somebody loaded the setup! self.on_ignore_clicked() elif self.data[0] == 'removed' and self.data[1] not in setupnames: # somebody unloaded the setup! self.on_ignore_clicked()
class CustomCombo(QComboBox): valueChanged = pyqtSignal(int) def __init__(self, container=None, box_data=None, init_state=None): QComboBox.__init__(self, parent=container) self.current = init_state for item in box_data: self.addItem('%s' % item) self.setCurrentIndex(self.current) self.currentIndexChanged[int].connect(self.index_changed) @pyqtSlot(int) def index_changed(self, index): # self.setDisabled(True) self.valueChanged[int].emit(index) def mousePressEvent(self, e): if e.button() == Qt.RightButton: if self.parent(): self.parent().mousePressEvent(e) else: QComboBox.mousePressEvent(self, e) def setValue(self, val): self.current = val self.setCurrentIndex(self.findText('%s' % val)) self.setEnabled(True) def value(self): return self.currentText()
class SetupDepPanelMixin(QObject): """Mixin to handle setup-dependent visibility. Note: You must explicity add the following class attribute in all classes using this mixin (A PyQt resctriction, see https://riverbankcomputing.com/pipermail/pyqt/2013-September/033199.html): `setWidgetVisible = SetupDepPanelMixin.setWidgetVisible` """ setupSpec = () setWidgetVisible = pyqtSignal(QWidget, bool, name='setWidgetVisible') def __init__(self, client, options): # pylint: disable=super-init-not-called setups = options.get('setups', '') self.setSetups(setups) client.register(self, 'session/mastersetup') def setSetups(self, setupSpec): self.setupSpec = setupSpec self.log.debug('setups are: %r', self.setupSpec) checkSetupSpec(self.setupSpec, '', log=self.log) def on_keyChange(self, key, value, time, expired): if key == 'session/mastersetup' and self.setupSpec: if hasattr(self, 'setWidgetVisible'): enabled = checkSetupSpec(self.setupSpec, value, log=self.log) self.setWidgetVisible.emit(self, enabled)
class CondCell(CellItem): standard_value = 'TrueTime' conds = ('LiveTime', 'TrueTime', 'ClockTime', 'counts') condChanged = pyqtSignal(str) def __init__(self, controller, parent=None, state=None): CellItem.__init__(self, controller, parent, state) index = int(self.conds.index(self.state)) self.cc = CustomCombo(container=self, box_data=self.conds, init_state=index) self.cc.valueChanged[int].connect(self.on_cb_changed) self.widgets.append(self.cc) self.set_layout() self.setMaximumWidth(100) def setValue(self, new_data): if new_data in CondCell.conds: self.cc.setValue(new_data) self.state = new_data self.condChanged.emit(new_data) def value(self): return self.cc.currentText() @pyqtSlot(int) def on_cb_changed(self, index): self.cellChanged.emit(self) self.condChanged.emit(self.value())
class CustomLED(ClickableOutputLed): """Just views to what state the Attenuator should/will be set.""" valueChanged = pyqtSignal(int) def __init__(self, container, state_names=('in', 'out'), state_colors=('blue', 'yellow'), initState=None): ClickableOutputLed.__init__(self, parent=container) self.stateActive = state_names[0] self.stateInactive = state_names[1] self._colorActive = state_colors[0] self._colorInactive = state_colors[1] self.current = initState self.set_ledcolor() def set_ledcolor(self): self.ledColor = self._colorActive \ if self.current == self._stateActive else self._colorInactive def setValue(self, val): self.current = val self.set_ledcolor() def value(self): return self.current def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self.setValue(self._stateInactive if self.current == self._stateActive else self._stateActive) self.valueChanged[int].emit(self.current) event.accept()
class MultiEntry(QWidget): addOrRemove = pyqtSignal() def __init__(self, parent, client, uifile): QWidget.__init__(self, parent) new_layout = QHBoxLayout() self.subwidget = QWidget(self) if uifile: uic.loadUi(uifile, self.subwidget) self.button = QToolButton(self) self.setButton('+') self.button.clicked.connect(self.on_button_click) new_layout.addWidget(self.subwidget) new_layout.addSpacerItem(QSpacerItem(15, 1, QSizePolicy.Fixed)) new_layout.addWidget(self.button) new_layout.setContentsMargins(0, 0, 0, 0) self.setLayout(new_layout) for ch in self.subwidget.findChildren(NicosWidget): ch.setClient(client) def setButton(self, plusminus): self.button.setText(plusminus) if plusminus == '+': self.button.setToolTip('Add another') else: self.button.setToolTip('Remove this') def on_button_click(self): self.addOrRemove.emit()
class CellItem(QWidget): standard_value = '' cellChanged = pyqtSignal('QWidget') def __init__(self, controller, parent=None, state=None): QWidget.__init__(self, parent) # update Request for parameter gets sent to controller self.controller = controller # contains widgets that are currently displayed self.widgets = [] self.state = state if state is not None else self.standard_value self.doubleclick = None # setting up layout: layout = QHBoxLayout() layout.setContentsMargins(0.1, 0.1, 0.1, 0.1) layout.setSpacing(1) self.setLayout(layout) def set_enabled(self, enabled): for widget in self.widgets: widget.setEnabled(enabled) # some self.reloadstate() def reloadstate(self): pass def is_enabled(self): return max(w.isEnabled() for w in self.widgets) def set_layout(self): for w in self.widgets: self.layout().addWidget(w) def setValue(self, val): self.label.setText('%s' % val) def value(self): return self.label.text() def disable(self): for w in self.widgets: w.setDisabled(True) def mouseDoubleClickEvent(self, e): for w in self.widgets: w.setEnabled(True) if self.doubleclick is not None: self.doubleclick = True e.ignore()
class AnnotatedWidget(QWidget): valueModified = pyqtSignal() valueChosen = pyqtSignal(object) def __init__(self, parent, inner, annotation): QWidget.__init__(self, parent) layout = self._layout = QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) self._inner = inner self._inner.valueModified.connect(self.valueModified) self._inner.valueChosen.connect(self.valueChosen) layout.addWidget(inner) layout.addWidget(QLabel(annotation, parent)) self.setLayout(layout) def getValue(self): return self._inner.getValue() def setFocus(self): self._inner.setFocus()
class SpinBoxWidget(QSpinBox): valueModified = pyqtSignal() valueChosen = pyqtSignal(object) def __init__(self, parent, curvalue, minmax, fmtstr='%.4g', allow_enter=False): QSpinBox.__init__(self, parent) self.setRange(minmax[0], minmax[1]) self.setValue(curvalue) self.valueChanged['int'].connect(lambda val: self.valueModified.emit()) if allow_enter: self.lineEdit().returnPressed.connect( lambda: self.valueChosen.emit(self.value())) def getValue(self): return self.value()
class ButtonWidget(QWidget): valueModified = pyqtSignal() valueChosen = pyqtSignal(object) def __init__(self, parent, values): QWidget.__init__(self, parent) layout = QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) self._values = {} for value in values: btn = QPushButton(str(value), self) self._values[btn] = value btn.clicked.connect(self.on_button_pressed) layout.addWidget(btn) self.setLayout(layout) def on_button_pressed(self): self.valueChosen.emit(self._values[self.sender()]) def getValue(self): return Ellipsis
class EditWidget(QLineEdit): valueModified = pyqtSignal() valueChosen = pyqtSignal(object) def __init__(self, parent, typ, curvalue, fmtstr='%.4g', minmax=None, allow_enter=False): QLineEdit.__init__(self, parent) self._typ = typ if typ is float: val = DoubleValidator(self) if minmax: # setRange doesn't work correctly in some Qt versions... val.setBottom(minmax[0]) if minmax[1] is not None: val.setTop(minmax[1]) self.setValidator(val) self.setText(fmtstr % curvalue) elif typ is int: val = QIntValidator(self) if minmax: val.setRange(minmax[0], minmax[1]) self.setValidator(val) self.setText(str(curvalue)) else: self.setText(str(curvalue)) self.textChanged.connect(lambda txt: self.valueModified.emit()) if allow_enter: self.returnPressed.connect( lambda: self.valueChosen.emit(self._typ(self.text()))) def getValue(self): return self._typ(self.text())
class LiveWidget(DefaultLiveWidget): """ Add a 'name' attribute to LiveWidget and emit 'clicked' signal when clicked """ clicked = pyqtSignal(str) def __init__(self, name, parent=None): DefaultLiveWidget.__init__(self, parent) self.setMinimumSize(150, 150) self.name = name def mousePressEvent(self, event): self.clicked.emit(self.name)
class DeviceParam(QWidget): editedParam = pyqtSignal() clickedRemoveButton = pyqtSignal(str) def __init__(self, param, valueWidget, isUnknownValue=False, parent=None): QWidget.__init__(self, parent) uic.loadUi( path.abspath( path.join(path.dirname(__file__), 'ui', 'deviceparam.ui')), self) self.placeholder.setVisible(False) self.param = param self.valueWidget = valueWidget self.isUnknownValue = isUnknownValue self.pushButtonRemove.clicked.connect( lambda: self.clickedRemoveButton.emit(self.param)) self.labelParam.setText(self.param + ':') self.horizontalLayout.addWidget(self.valueWidget) self.valueWidget.valueModified.connect(self.editedParam) self.valueWidget.valueChosen.connect(lambda _: self.editedParam.emit()) def getValue(self): return self.valueWidget.getValue()
class ComboWidget(QComboBox): valueModified = pyqtSignal() valueChosen = pyqtSignal(object) def __init__(self, parent, values, curvalue, add_other=False): QComboBox.__init__(self, parent) self._values = sorted(values, key=str) self._textvals = list(map(str, self._values)) self._add_other = add_other if add_other: self._values.append(Ellipsis) self._textvals.append('<other value>') self.addItems(self._textvals) if curvalue in self._values: self.setCurrentIndex(self._values.index(curvalue)) elif add_other: self.setCurrentIndex(len(self._values) - 1) self.currentIndexChanged['int'].connect( lambda idx: self.valueModified.emit()) def getValue(self): return self._values[self._textvals.index(self.currentText())]
class MeasElement(QObject): """Represents one setting for a measurement that can be manipulated.""" LABEL = '' ORDER = 1 changed = pyqtSignal(object) def __init__(self, eltype, client, value=None, extra=None): """Initialize widget contents, if necessary.""" QObject.__init__(self) self.eltype = eltype self.value = value self.extra = extra self._widget = None self.clientUpdate(client) def getLabel(self): """Return label for the element.""" if self.LABEL: return self.LABEL return self.eltype.capitalize() def clientUpdate(self, client): """Update internal info from daemon.""" def createWidget(self, parent, client): """Create and return a Qt widget for editing this element.""" def destroyWidget(self): """Destroy the currently created widget.""" if self._widget: self._widget.deleteLater() self._widget = None def getValue(self): """Return currently selected value.""" return self.value def getDispValue(self): """Return a form of the value to be displayed.""" return str(self.getValue()) def otherChanged(self, eltype, value): """Hook to be called when a sibling element changed."""
class DetachedWindow(QMainWindow): closed = pyqtSignal(object) def __init__(self, title, parent): self.tabIdx = -1 QMainWindow.__init__(self, parent) self.setWindowTitle(title) self.setWindowModality(Qt.NonModal) self.sgroup = SettingGroup(title) with self.sgroup as settings: loadBasicWindowSettings(self, settings) @pyqtSlot(QWidget, bool) def setWidgetVisibleSlot(self, widget, visible): self.setVisible(visible) def setWidget(self, widget): self.setCentralWidget(widget) widget.show() def closeEvent(self, event): with self.sgroup as settings: settings.setValue('detached', False) self.closed.emit(self) self.deleteLater() def moveEvent(self, event): QMainWindow.moveEvent(self, event) self.saveSettings() def resizeEvent(self, event): QMainWindow.resizeEvent(self, event) self.saveSettings() def saveSettings(self, detached=True): with self.sgroup as settings: settings.setValue('detached', detached) settings.setValue('geometry', self.saveGeometry()) settings.setValue('windowstate', self.saveState())
class StandaloneHistoryWindow(DlgUtils, BaseHistoryWindow, QMainWindow): newValue = pyqtSignal(object) def __init__(self, app): QMainWindow.__init__(self, None) self.app = app self.client = app # used by the NewViewDialog # this is done in Panel.__init__ for the panel version self.settings = CompatSettings() self.loadSettings(self.settings) BaseHistoryWindow.__init__(self) self.splitter.setSizes([20, 80]) DlgUtils.__init__(self, 'History viewer') self.actionAttachElog.setVisible(False) self.splitter.restoreState(self.splitterstate) self.setCentralWidget(self.splitter) self.newValue.connect(self.newvalue_callback) for toolbar in self.getToolbars(): self.addToolBar(toolbar) for menu in self.getMenus(): self.menuBar().addMenu(menu) self.actionFitLinear.trigger() self.statusBar = QStatusBar(self) self.setStatusBar(self.statusBar) def gethistory_callback(self, key, fromtime, totime): return self.app.history(None, key, fromtime, totime) def closeEvent(self, event): self.saveSettings(self.settings) return QMainWindow.closeEvent(self, event)
class TimelineWidget(QGraphicsView): """General widget to display timeline with a list of ordered timepoints. A timepoint is selectable via click and the timepointSelected signal can be used to react to it.""" timepointSelected = pyqtSignal(object) # datetime.datetime object # general layout and design parameters TIMEPOINT_DIAMETER = 30 SELECTION_DIAMETER = 40 TIMEPOINT_SPACING = 50 TIMELINE_WIDTH = 5 LABEL_SPACING = 20 MARGIN_HORIZONTAL = 5 STRFTIME_FMT = '%H:%M:%S\n%Y-%m-%d' def __init__(self, parent=None): QGraphicsView.__init__(self, QGraphicsScene(), parent) self.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) self.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform) # margins set to 0 to simplify calculations self.setContentsMargins(0, 0, 0, 0) self.setViewportMargins(0, 0, 0, 0) # full viewport updates required to avoid optical double selections # caused by scrolling self.setViewportUpdateMode(QGraphicsView.FullViewportUpdate) self._time_points = [] self._time_point_items = {} self._selection_item = None # start with at least one time point (current time) to be able # to reuse size calculation methods for the inital size self.setTimePoints([datetime.now()]) @property def time_points(self): """Sorted list of all timepoints (sorted new to old)""" return self._time_points @property def selected_time_point(self): """Get the selected timeout as datetime object. None if there is no selection.""" if self._selection_item is None: return None return self._time_point_items[self._selection_item] @property def previous_time_point(self): """Get the timepoint before (older than) the currently selected one as datetime object. None if there is no selection""" try: index = self._time_points.index(self.selected_time_point) return self._time_points[index + 1] except (ValueError, IndexError): return None def resizeEvent(self, event): """Clear and readd all items on resize. Avoids complex scaling.""" self.scene().clear() self.setTimePoints(self._time_points) def setTimePoints(self, time_points): """Sets and list of datetime objects as timepoints, sets up all necessary graphics items and adjusts sizes.""" self.scene().clear() self._selection_item = None # store the timepoints sorted from new to old self._time_points = list(reversed(sorted(time_points))) self._time_point_items = {} # draw the timeline self._timeline = self._add_timeline() # draw the time points self._add_time_points() # update the scene size and a slightly larger widget size to avoid # superfluous scrolling (and displaying of scroll bars) size = self.scene().itemsBoundingRect().size() self.setSceneRect(0, 0, size.width(), size.height() - 5) if time_points: self.setMinimumWidth(size.width() * 1.2) self.setMaximumWidth(size.width() * 1.2) def mousePressEvent(self, event): """Handle mouse press events to support item selection.""" item = self.itemAt(event.pos()) if item in self._time_point_items: self._select_item(item) return QGraphicsView.mousePressEvent(self, event) def _select_item(self, item): """Select the given item by drawing a colored circle beneath the selected item (so it looks like a ring around it. Also emits the timepointSelected signal.""" # The selection_item used to signal the selection of a timepoint # is always the same and is only moved. if self._selection_item is None: self._selection_item = QGraphicsEllipseItem( 0, 0, self.SELECTION_DIAMETER, self.SELECTION_DIAMETER) # The used color is a cubical to the time point color self._selection_item.setBrush(QBrush(QColor(0x70, 0xbb, 0x00))) self._selection_item.setPen(QPen(0)) self.scene().addItem(self._selection_item) # center position of the timepoint circle center_x = item.pos().x() + self.TIMEPOINT_DIAMETER / 2 center_y = item.pos().y() + self.TIMEPOINT_DIAMETER / 2 # move selection item self._selection_item.setPos(center_x - self.SELECTION_DIAMETER / 2, center_y - self.SELECTION_DIAMETER / 2) # store the selection_item like a timepoint item (using the timepoint # of the selected item) self._time_point_items[self._selection_item] = \ self._time_point_items[item] # emit signal at the end to ensure a valid internal state before # anything can react to it self.timepointSelected.emit(self._time_point_items[item]) def _add_timeline(self): """Draw the timeline.""" # height is either the necessary space to display all items or the # maximal available display size, so it's looks nicely in larger # windows and enables scrolling in smaller ones. height = self.TIMEPOINT_DIAMETER * len(self._time_points) height += self.TIMEPOINT_SPACING * len(self._time_points) height = max(height, self.viewport().height()) # draw the timeline left aligned with enough space to draw the items # and the selection ring. x = self.MARGIN_HORIZONTAL + (self.SELECTION_DIAMETER / 2) # position the line on the left side of the item item = QGraphicsLineItem(0, 0, 0, height) # The used color for the timeline is the lightest one of the FRM II # colors item.setPen(QPen(QBrush(QColor(0xa3, 0xc1, 0xe7)), self.TIMELINE_WIDTH)) self.scene().addItem(item) # move the whole item to the desired timeline position item.setPos(x, 0) return item def _add_time_points(self): """Add all time point items.""" if not self._time_points: return timeline_pos = self._timeline.pos() timeline_size = self._timeline.boundingRect().size() height = timeline_size.height() # time points are always equally distributed on the timeline spacing = height / float(len(self._time_points)) center_x = timeline_pos.x() # add half of the items spacing on the top and bottom of the timeline start = timeline_pos.y() - spacing / 2 for i, entry in enumerate(self._time_points): self._add_time_point(center_x, start + spacing * (i + 1), entry) def _add_time_point(self, center_x, center_y, time_point): """Add a single time point item.""" x = center_x - (self.TIMEPOINT_DIAMETER / 2) y = center_y - (self.TIMEPOINT_DIAMETER / 2) # Create the acutal time point item time_point_item = QGraphicsEllipseItem(0, 0, self.TIMEPOINT_DIAMETER, self.TIMEPOINT_DIAMETER) # The used color is the strongest one of the FRM II colors. time_point_item.setBrush(QBrush(QColor(0x00, 0x71, 0xbb))) time_point_item.setPen(QPen(0)) self.scene().addItem(time_point_item) time_point_item.setPos(x, y) # place the time point item above the timeline and the selection item time_point_item.setZValue(2) # Create the label of the time point showing the time in the # defined strftime format on the right side of the time point item. label = QGraphicsTextItem(time_point.strftime(self.STRFTIME_FMT)) label.setFont(QFont('Monospace')) label_height = label.boundingRect().height() # minor height adjustment label_y = y - label_height / 6 self.scene().addItem(label) label.setPos(x + self.SELECTION_DIAMETER + self.LABEL_SPACING, label_y) # store references to the item and the timepoint in the same dict # to be able to use it for forward and reverse lookup self._time_point_items[time_point] = time_point_item self._time_point_items[time_point_item] = time_point
class Cmdlet(QWidget): name = '' category = '' cmdletUp = pyqtSignal() cmdletDown = pyqtSignal() cmdletRemove = pyqtSignal() valueModified = pyqtSignal() def __init__(self, parent, client, uifile): self.client = client QWidget.__init__(self, parent) loadUi(self, uifile) loadUi(self.buttons, 'cmdlets/buttons.ui') self.buttons.upBtn.clicked.connect(self.cmdletUp) self.buttons.downBtn.clicked.connect(self.cmdletDown) self.buttons.delBtn.clicked.connect(self.cmdletRemove) def removeSelf(self): self.parent().layout().removeWidget(self) self.hide() def changed(self, *args): """Should be called whenever any value in the cmdlet changes.""" self.valueModified.emit() def getValues(self): """Return a dict with the values of the cmdlet. Values should have a name that is the same for the same logical value in multiple cmdlets, e.g. "dev" for the device in a command. """ return {} def _getDeviceList(self, special_clause=''): """Helper for getting a list of devices for manipulation.""" exp = getattr(self.parent(), 'expertmode', False) if exp: clause = special_clause else: clause = ('(dn in session.explicit_devices or ' '("nicos.core.mixins.AutoDevice" in d.classes and ' 'dn.split(".")[0] in session.explicit_devices))') if special_clause: clause += ' and ' + special_clause # special construction to get AutoDevices like slit.centerx which is # useful to make scans over return self.client.getDeviceList('nicos.core.device.Moveable', only_explicit=False, special_clause=clause) def _getDeviceRepr(self, devname): """Return bare ``dev`` if the device is in the NICOS user namespace, else ``'dev'`` in quotes. """ if self.client.eval(devname, None) is None: return srepr(devname) return devname def _setDevice(self, values): """Helper for setValues for setting a device combo box.""" if 'dev' in values: idx = self.device.findText(values['dev']) if idx > -1: self.device.setCurrentIndex(idx) def setValues(self, values): """Set values of the cmdlet with values from the argument. Unknown values must be ignored. """ def markValid(self, ctl, condition): """Return boolean condition, and also mark the offending widget. For use in isValid(). """ if condition: setBackgroundColor(ctl, Qt.white) else: setBackgroundColor(ctl, invalid) if isinstance(ctl, QAbstractSpinBox): # also mark the inner line-edit return self.markValid(ctl.lineEdit(), condition) return condition def isValid(self): """Check if all entered data is valid. This method can change widget styles to indicate invalid data with the markValid() method if wanted. """ return True def generate(self, mode): """Generate code for the commandlet. *mode* is 'python' or 'simple'. Should generate a string of lines, complete with newlines. """ return ''