def add_row(self, row, name, unit, value_property, target_property, range_low, range_high): layout = self.layout() target_label = Qt.QLabel('Target {}:'.format(name)) target_label.setAlignment(Qt.Qt.AlignRight | Qt.Qt.AlignVCenter) #target_label.setSizePolicy(Qt.QSizePolicy.Expanding, Qt.QSizePolicy.Expanding) layout.addWidget(target_label, row, 0) target = Qt.QLineEdit() target.setValidator( Qt.QDoubleValidator(range_low, range_high, 1, parent=self)) update = self.subscribe( target_property, callback=lambda value: target.setText(str(value))) def editing_finished(): try: value = float(target.text()) update(value) except Exception as e: Qt.QMessageBox.warning(self, 'Could not set {}'.format(name), e.args[0]) target.editingFinished.connect(editing_finished) layout.addWidget(target, row, 1) label = Qt.QLabel('{}\t{}: - {}'.format(unit, name, unit)) layout.addWidget(label, row, 2) self.subscribe(value_property, callback=lambda value: label.setText( '{}\t{}: {} {}'.format(unit, name, value, unit)), readonly=True)
def __init__(self, parent=None): super(LaueFormWidget, self).__init__(parent) layout = Qt.QFormLayout(self) self._nCellsLineEdit = IntLineEdit(2, self) layout.addRow("Number of unit cells:", self._nCellsLineEdit) self._oversamplingLineEdit = IntLineEdit(2, self) layout.addRow("Oversampling:", self._oversamplingLineEdit) self._hLineEdit = Qt.QLineEdit(self) self._hLineEdit.setValidator(Qt.QDoubleValidator()) layout.addRow("H:", self._hLineEdit) self._kLineEdit = Qt.QLineEdit(self) self._kLineEdit.setValidator(Qt.QDoubleValidator()) layout.addRow("K:", self._kLineEdit) pushButton = Qt.QPushButton("Run and Save", self) pushButton.clicked.connect(self.compute) layout.addRow(pushButton)
def get_numeric_validator(self, property, type): if type == 'Float': validator = Qt.QDoubleValidator() if property in self.RANGE_HINTS: min, max, decimals = self.RANGE_HINTS[property] else: min, max, decimals = FLOAT_MIN, FLOAT_MAX, FLOAT_DECIMALS if decimals is not None: validator.setDecimals(decimals) if type == 'Int': validator = Qt.QIntValidator() if property in self.RANGE_HINTS: min, max = self.RANGE_HINTS[property] else: min, max = INT_MIN, INT_MAX if min is not None: validator.setBottom(min) if max is not None: validator.setTop(max) return validator
def make_numeric_widget(self, prop, prop_data): andor_type = prop_data['andor_type'] if andor_type == 'Float': validator = Qt.QDoubleValidator() coerce_type = float else: # andor_type == 'Int' validator = Qt.QIntValidator() coerce_type = int widget = Qt.QLineEdit() widget.setValidator(validator) def receive_update(value): widget.setText(f'{value:.6g}') update = self.subscribe(self.PROPERTY_ROOT + prop, callback=receive_update) if update is None: raise TypeError('{} is not a writable property!'.format(prop)) def editing_finished(): try: value = coerce_type(widget.text()) update(value) except ValueError as e: # from the coercion Qt.QMessageBox.warning(self, 'Invalid Value', e.args[0]) except rpc_client.RPCError as e: # from the update if e.args[0].find('OUTOFRANGE') != -1: valid_min, valid_max = getattr(self.scope.camera, prop+'_range') if value < valid_min: update(valid_min) elif value > valid_max: update(valid_max) else: if e.args[0].find('NOTWRITABLE') != -1: error = 'Given the camera state, {} is not modifiable.'.format(prop) else: error = 'Could not set {} ({}).'.format(prop, e.args[0]) Qt.QMessageBox.warning(self, 'Invalid Value', error) widget.editingFinished.connect(editing_finished) return widget
def make_stage_axis_pos_widget(self, property, axis_max_val): widget = Qt.QWidget() vlayout = Qt.QVBoxLayout() vlayout.setSpacing(0) vlayout.setContentsMargins(0, 0, 0, 0) widget.setLayout(vlayout) axis_name = property.split('.')[-1] props = self.scope.properties.properties # dict of tracked properties, updated by property client # [low limits status indicator] [-------<slider>-------] [high limits status indicator] slider_layout = Qt.QHBoxLayout() l, t, r, b = slider_layout.getContentsMargins() slider_layout.setContentsMargins(l, 0, r, 0) slider_layout.setSpacing(5) low_limit_status_label = Qt.QLabel() # NB: *_limit_status_label pixmaps are set here so that layout does not jump when limit status RPC property updates # are first received low_limit_status_label.setPixmap( self.limit_pixmaps_and_tooltips.low_no_limit_pm) slider_layout.addWidget(low_limit_status_label) pos_slider_factor = 1e3 pos_slider = Qt.QSlider(Qt.Qt.Horizontal) pos_slider.setEnabled(False) pos_slider.setRange(0, pos_slider_factor * axis_max_val) pos_slider.setValue(0) slider_layout.addWidget(pos_slider) high_limit_status_label = Qt.QLabel() high_limit_status_label.setPixmap( self.limit_pixmaps_and_tooltips.high_no_limit_pm) slider_layout.addWidget(high_limit_status_label) vlayout.addLayout(slider_layout) at_ls_property = self.PROPERTY_ROOT + 'stage.at_{}_low_soft_limit'.format( axis_name) at_lh_property = self.PROPERTY_ROOT + 'stage.at_{}_low_hard_limit'.format( axis_name) at_hs_property = self.PROPERTY_ROOT + 'stage.at_{}_high_soft_limit'.format( axis_name) at_hh_property = self.PROPERTY_ROOT + 'stage.at_{}_high_hard_limit'.format( axis_name) def at_low_limit_prop_changed(_): try: at_s = props[at_ls_property] at_h = props[at_lh_property] except KeyError: return if at_s and at_h: pm = self.limit_pixmaps_and_tooltips.low_hard_and_soft_limits_pm tt = self.limit_pixmaps_and_tooltips.low_hard_and_soft_limits_tt elif at_s: pm = self.limit_pixmaps_and_tooltips.low_soft_limit_pm tt = self.limit_pixmaps_and_tooltips.low_soft_limit_tt elif at_h: pm = self.limit_pixmaps_and_tooltips.low_hard_limit_pm tt = self.limit_pixmaps_and_tooltips.low_hard_limit_tt else: pm = self.limit_pixmaps_and_tooltips.low_no_limit_pm tt = self.limit_pixmaps_and_tooltips.low_no_limit_tt low_limit_status_label.setPixmap(pm) low_limit_status_label.setToolTip(tt) self.subscribe(at_ls_property, at_low_limit_prop_changed, readonly=True) self.subscribe(at_lh_property, at_low_limit_prop_changed, readonly=True) def at_high_limit_prop_changed(_): try: at_s = props[at_hs_property] at_h = props[at_hh_property] except KeyError: return if at_s and at_h: pm = self.limit_pixmaps_and_tooltips.high_hard_and_soft_limits_pm tt = self.limit_pixmaps_and_tooltips.high_hard_and_soft_limits_tt elif at_s: pm = self.limit_pixmaps_and_tooltips.high_soft_limit_pm tt = self.limit_pixmaps_and_tooltips.high_soft_limit_tt elif at_h: pm = self.limit_pixmaps_and_tooltips.high_hard_limit_pm tt = self.limit_pixmaps_and_tooltips.high_hard_limit_tt else: pm = self.limit_pixmaps_and_tooltips.high_no_limit_pm tt = self.limit_pixmaps_and_tooltips.high_no_limit_tt high_limit_status_label.setPixmap(pm) high_limit_status_label.setToolTip(tt) self.subscribe(at_hs_property, at_high_limit_prop_changed, readonly=True) self.subscribe(at_hh_property, at_high_limit_prop_changed, readonly=True) # [stop] [low soft limit text edit] [position text edit] [high soft limit text edit] [reset high soft limit button] buttons_layout = Qt.QHBoxLayout() l, t, r, b = buttons_layout.getContentsMargins() buttons_layout.setSpacing(5) buttons_layout.setContentsMargins(l, 0, r, 0) stop_button = Qt.QPushButton( widget.style().standardIcon(Qt.QStyle.SP_BrowserStop), '') stop_button.setToolTip( 'Stop movement along {} axis.'.format(axis_name)) stop_button.setEnabled(False) buttons_layout.addWidget(stop_button) low_limit_text_widget = FocusLossSignalingLineEdit() low_limit_text_widget.setMaxLength(8) low_limit_text_validator = Qt.QDoubleValidator() low_limit_text_validator.setBottom(0) low_limit_text_widget.setValidator(low_limit_text_validator) buttons_layout.addWidget(low_limit_text_widget) pos_text_widget = FocusLossSignalingLineEdit() pos_text_widget.setMaxLength(8) pos_text_validator = Qt.QDoubleValidator() pos_text_widget.setValidator(pos_text_validator) buttons_layout.addWidget(pos_text_widget) high_limit_text_widget = FocusLossSignalingLineEdit() high_limit_text_widget.setMaxLength(8) high_limit_text_validator = Qt.QDoubleValidator() high_limit_text_validator.setTop(axis_max_val) high_limit_text_widget.setValidator(high_limit_text_validator) buttons_layout.addWidget(high_limit_text_widget) reset_limits_button = Qt.QPushButton('Reset limits') reset_limits_button.setToolTip( 'Reset {} soft min and max to the smallest \n and largest acceptable values, respectively.' .format(axis_name)) buttons_layout.addWidget(reset_limits_button) vlayout.addLayout(buttons_layout) def moving_along_axis_changed(value): stop_button.setEnabled(value) self.subscribe('{}stage.moving_along_{}'.format( self.PROPERTY_ROOT, axis_name), moving_along_axis_changed, readonly=True) def stop_moving_along_axis(): try: self.get_scope_attr(self.PROPERTY_ROOT + 'stage.stop_{}'.format(axis_name))() except rpc_client.RPCError as e: error = 'Could not stop movement along {} axis ({}).'.format( axis_name, e.args[0]) Qt.QMessageBox.warning(self, 'RPC Exception', error) # TODO: verify the below doesn't blow up without indexing the # overloaded clicked signal as [bool] stop_button.clicked.connect(stop_moving_along_axis) # low limit sub-widget low_limit_property = self.PROPERTY_ROOT + 'stage.{}_low_soft_limit'.format( axis_name) handling_low_soft_limit_change = util.Condition( ) # start out false, except when used as with-block context manager def low_limit_prop_changed(value): if handling_low_soft_limit_change: return with handling_low_soft_limit_change: low_limit_text_widget.setText(str(value)) pos_text_validator.setBottom(value) high_limit_text_validator.setBottom(value) update_low_limit = self.subscribe(low_limit_property, low_limit_prop_changed) if update_low_limit is None: low_limit_text_widget.setEnabled(False) else: def submit_low_limit_text(): if handling_low_soft_limit_change: return with handling_low_soft_limit_change: try: new_low_limit = float(low_limit_text_widget.text()) except ValueError: return try: update_low_limit(new_low_limit) except rpc_client.RPCError as e: error = 'Could not set {} axis to {} ({}).'.format( axis_name, new_low_limit, e.args[0]) Qt.QMessageBox.warning(self, 'RPC Exception', error) low_limit_text_widget.returnPressed.connect(submit_low_limit_text) def low_limit_text_focus_lost(): low_limit_text_widget.setText( str(props.get(low_limit_property, ''))) low_limit_text_widget.focus_lost.connect(low_limit_text_focus_lost) # position sub-widget handling_pos_change = util.Condition() def position_changed(value): if handling_pos_change: return with handling_pos_change: pos_text_widget.setText(str(value)) pos_slider.setValue(int(value * pos_slider_factor)) self.subscribe(property, position_changed, readonly=True) get_pos = getattr(self.scope.stage, '_get_{}'.format(axis_name)) set_pos = getattr(self.scope.stage, '_set_{}'.format(axis_name)) def submit_pos_text(): if handling_pos_change: return with handling_pos_change: try: new_pos = float(pos_text_widget.text()) except ValueError: return if new_pos != get_pos(): try: set_pos(new_pos, async='fire_and_forget') except rpc_client.RPCError as e: error = 'Could not set {} axis to {} ({}).'.format( axis_name, new_pos, e.args[0]) Qt.QMessageBox.warning(self, 'RPC Exception', error) pos_text_widget.returnPressed.connect(submit_pos_text) def pos_text_focus_lost(): pos_text_widget.setText(str(props.get(property, ''))) pos_text_widget.focus_lost.connect(pos_text_focus_lost) # high limit sub-widget high_limit_property = self.PROPERTY_ROOT + 'stage.{}_high_soft_limit'.format( axis_name) handling_high_soft_limit_change = util.Condition() def high_limit_prop_changed(value): if handling_high_soft_limit_change: return with handling_high_soft_limit_change: high_limit_text_widget.setText(str(value)) pos_text_validator.setTop(value) low_limit_text_validator.setTop(value) update_high_limit = self.subscribe(high_limit_property, high_limit_prop_changed) if update_high_limit is None: high_limit_text_widget.setEnabled(False) else: def submit_high_limit_text(): if handling_high_soft_limit_change: return with handling_high_soft_limit_change: try: new_high_limit = float(high_limit_text_widget.text()) except ValueError: return try: update_high_limit(new_high_limit) except rpc_client.RPCError as e: error = 'Could not set {} axis to {} ({}).'.format( axis_name, new_high_limit, e.args[0]) Qt.QMessageBox.warning(self, 'RPC Exception', error) high_limit_text_widget.returnPressed.connect( submit_high_limit_text) def high_limit_text_focus_lost(): high_limit_text_widget.setText( str(props.get(high_limit_property, ''))) high_limit_text_widget.focus_lost.connect( high_limit_text_focus_lost) def reset_limits_button_clicked(_): update_low_limit(0.0) self.get_scope_attr( self.PROPERTY_ROOT + 'stage.reset_{}_high_soft_limit'.format(axis_name))() # TODO: verify the below doesn't blow up without indexing the # overloaded clicked signal as [bool] reset_limits_button.clicked.connect(reset_limits_button_clicked) # We do not receive events for z high soft limit changes initiated by means other than assigning # to scope.stage.z_high_soft_limit or calling scope.stage.reset_z_high_soft_limit(). However, # the scope's physical interface does not offer any way to modify z high soft limit, with one # possible exception: it would make sense for the limit to change with objective in order to prevent # head crashing. In case that happens, we refresh z high soft limit upon objective change. # TODO: verify that this is never needed and get rid of it if so if axis_name is 'z': def objective_changed(_): if handling_high_soft_limit_change: return with handling_high_soft_limit_change: high_limit_text_widget.setText( str( self.get_scope_attr(self.PROPERTY_ROOT + 'stage.z_high_soft_limit'))) self.subscribe(self.PROPERTY_ROOT + 'nosepiece.position', objective_changed, readonly=True) return widget
def make_stage_axis_pos_widget(self, rppath, pt, stage_rppath, axis_name, axis_max_val, provoke_update_rppath=None): device = self.pattr(stage_rppath) widget = Qt.QWidget() vlayout = Qt.QVBoxLayout() widget.setLayout(vlayout) handling_low_soft_limit_change = False handling_high_soft_limit_change = False handling_pos_change = False props = self.scope_properties.properties low_limit_rppath = '{}.{}_low_soft_limit'.format( stage_rppath, axis_name) low_limit_ppath = self.PROPERTY_ROOT + low_limit_rppath pos_ppath = self.PROPERTY_ROOT + rppath high_limit_rppath = '{}.{}_high_soft_limit'.format( stage_rppath, axis_name) high_limit_ppath = self.PROPERTY_ROOT + high_limit_rppath # [low limits status indicator] [-------<slider>-------] [high limits status indicator] hlayout = Qt.QHBoxLayout() low_limit_status_label = Qt.QLabel() # NB: *_limit_status_label pixmaps are set here so that layout does not jump when limit status RPC property updates # are first received low_limit_status_label.setPixmap( self.limit_pixmaps_and_tooltips.low_no_limit_pm) hlayout.addWidget(low_limit_status_label) pos_slider_factor = 1e5 pos_slider = Qt.QSlider(Qt.Qt.Horizontal) pos_slider.setEnabled(False) pos_slider.setRange(0, pos_slider_factor * axis_max_val) hlayout.addWidget(pos_slider) high_limit_status_label = Qt.QLabel() high_limit_status_label.setPixmap( self.limit_pixmaps_and_tooltips.high_no_limit_pm) hlayout.addWidget(high_limit_status_label) vlayout.addLayout(hlayout) at_ls_pname = '{}{}.at_{}_low_soft_limit'.format( self.PROPERTY_ROOT, stage_rppath, axis_name) at_lh_pname = '{}{}.at_{}_low_hard_limit'.format( self.PROPERTY_ROOT, stage_rppath, axis_name) def at_low_limit_prop_changed(_): try: at_s = props[at_ls_pname] at_h = props[at_lh_pname] except KeyError: return if at_s and at_h: pm = self.limit_pixmaps_and_tooltips.low_hard_and_soft_limits_pm tt = self.limit_pixmaps_and_tooltips.low_hard_and_soft_limits_tt elif at_s: pm = self.limit_pixmaps_and_tooltips.low_soft_limit_pm tt = self.limit_pixmaps_and_tooltips.low_soft_limit_tt elif at_h: pm = self.limit_pixmaps_and_tooltips.low_hard_limit_pm tt = self.limit_pixmaps_and_tooltips.low_hard_limit_tt else: pm = self.limit_pixmaps_and_tooltips.low_no_limit_pm tt = self.limit_pixmaps_and_tooltips.low_no_limit_tt low_limit_status_label.setPixmap(pm) low_limit_status_label.setToolTip(tt) self.subscribe(at_ls_pname, at_low_limit_prop_changed) self.subscribe(at_lh_pname, at_low_limit_prop_changed) at_hs_pname = '{}{}.at_{}_high_soft_limit'.format( self.PROPERTY_ROOT, stage_rppath, axis_name) at_hh_pname = '{}{}.at_{}_high_hard_limit'.format( self.PROPERTY_ROOT, stage_rppath, axis_name) def at_high_limit_prop_changed(_): try: at_s = props[at_hs_pname] at_h = props[at_hh_pname] except KeyError: return if at_s and at_h: pm = self.limit_pixmaps_and_tooltips.high_hard_and_soft_limits_pm tt = self.limit_pixmaps_and_tooltips.high_hard_and_soft_limits_tt elif at_s: pm = self.limit_pixmaps_and_tooltips.high_soft_limit_pm tt = self.limit_pixmaps_and_tooltips.high_soft_limit_tt elif at_h: pm = self.limit_pixmaps_and_tooltips.high_hard_limit_pm tt = self.limit_pixmaps_and_tooltips.high_hard_limit_tt else: pm = self.limit_pixmaps_and_tooltips.high_no_limit_pm tt = self.limit_pixmaps_and_tooltips.high_no_limit_tt high_limit_status_label.setPixmap(pm) high_limit_status_label.setToolTip(tt) self.subscribe(at_hs_pname, at_high_limit_prop_changed) self.subscribe(at_hh_pname, at_high_limit_prop_changed) # [stop] [low soft limit text edit] [position text edit] [high soft limit text edit] [reset high soft limit button] hlayout = Qt.QHBoxLayout() stop_button = Qt.QPushButton( widget.style().standardIcon(Qt.QStyle.SP_BrowserStop), '') stop_button.setToolTip('Stop movement along {}.'.format(rppath)) stop_button.setEnabled(False) hlayout.addWidget(stop_button) low_limit_text_widget = FocusLossSignalingLineEdit() low_limit_text_widget.setMaxLength(8) low_limit_text_validator = Qt.QDoubleValidator() low_limit_text_validator.setBottom(0) low_limit_text_widget.setValidator(low_limit_text_validator) hlayout.addWidget(low_limit_text_widget) pos_text_widget = FocusLossSignalingLineEdit() pos_text_widget.setMaxLength(8) pos_text_validator = Qt.QDoubleValidator() pos_text_widget.setValidator(pos_text_validator) hlayout.addWidget(pos_text_widget) high_limit_text_widget = FocusLossSignalingLineEdit() high_limit_text_widget.setMaxLength(8) high_limit_text_validator = Qt.QDoubleValidator() high_limit_text_validator.setTop(axis_max_val) high_limit_text_widget.setValidator(high_limit_text_validator) hlayout.addWidget(high_limit_text_widget) reset_limits_button = Qt.QPushButton('Reset limits') reset_limits_button.setToolTip( 'Reset {} soft min and max to the smallest \n and largest acceptable values, respectively.' .format(axis_name)) hlayout.addWidget(reset_limits_button) vlayout.addLayout(hlayout) def moving_along_axis_changed(value): stop_button.setEnabled(value) def stop_moving_along_axis(): try: self.pattr('{}.stop_{}'.format(stage_rppath, axis_name))() except rpc_client.RPCError as e: error = 'Could not stop movement along {} ({}).'.format( rppath, e.args[0]) Qt.QMessageBox.warning(self, 'RPC Exception', error) def low_limit_prop_changed(value): nonlocal handling_low_soft_limit_change if handling_low_soft_limit_change: return handling_low_soft_limit_change = True try: low_limit_text_widget.setText(str(value)) pos_text_validator.setBottom(value) high_limit_text_validator.setBottom(value) finally: handling_low_soft_limit_change = False def submit_low_limit_text(): nonlocal handling_low_soft_limit_change if handling_low_soft_limit_change: return handling_low_soft_limit_change = True try: new_low_limit = float(low_limit_text_widget.text()) try: update_low_limit(new_low_limit) except rpc_client.RPCError as e: error = 'Could not set {}.{} to {} ({}).' error = error.format(stage_rppath, axis_name, new_low_limit, e.args[0]) Qt.QMessageBox.warning(self, 'RPC Exception', error) except ValueError: pass finally: handling_low_soft_limit_change = False def low_limit_text_focus_lost(): low_limit_text_widget.setText(str(props.get(low_limit_ppath, ''))) def pos_prop_changed(value): nonlocal handling_pos_change if handling_pos_change: return handling_pos_change = True try: pos_text_widget.setText(str(value)) pos_slider.setValue(value * pos_slider_factor) finally: handling_pos_change = False def submit_pos_text(): nonlocal handling_pos_change if handling_pos_change: return handling_pos_change = True try: new_pos = float(pos_text_widget.text()) if new_pos != get_pos(): try: set_pos(new_pos, async='fire_and_forget') except rpc_client.RPCError as e: error = 'Could not set {} to {} ({}).'.format( rppath, new_pos, e.args[0]) Qt.QMessageBox.warning(self, 'RPC Exception', error) except ValueError: pass finally: handling_pos_change = False def pos_text_focus_lost(): pos_text_widget.setText(str(props.get(pos_ppath, ''))) def high_limit_prop_changed(value): nonlocal handling_high_soft_limit_change if handling_high_soft_limit_change: return handling_high_soft_limit_change = True try: high_limit_text_widget.setText(str(value)) pos_text_validator.setTop(value) low_limit_text_validator.setTop(value) finally: handling_high_soft_limit_change = False def submit_high_limit_text(): nonlocal handling_high_soft_limit_change if handling_high_soft_limit_change: return handling_high_soft_limit_change = True try: new_high_limit = float(high_limit_text_widget.text()) try: update_high_limit(new_high_limit) except rpc_client.RPCError as e: error = 'Could not set {}.{} to {} ({}).' error = error.format(stage_rppath, axis_name, new_high_limit, e.args[0]) Qt.QMessageBox.warning(self, 'RPC Exception', error) except ValueError: pass finally: handling_high_soft_limit_change = False def high_limit_text_focus_lost(): high_limit_text_widget.setText(str(props.get(high_limit_ppath, ''))) def reset_limits_button_clicked(_): update_low_limit(0.0) self.pattr('{}.reset_{}_high_soft_limit'.format( stage_rppath, axis_name))() stop_button.clicked[bool].connect(stop_moving_along_axis) self.subscribe( '{}{}.moving_along_{}'.format(self.PROPERTY_ROOT, stage_rppath, axis_name), moving_along_axis_changed) update_low_limit = self.subscribe(low_limit_ppath, low_limit_prop_changed) if update_low_limit is None: raise TypeError( '{} is not a writable property!'.format(low_limit_ppath)) low_limit_text_widget.returnPressed.connect(submit_low_limit_text) low_limit_text_widget.focus_lost.connect(low_limit_text_focus_lost) self.subscribe(pos_ppath, pos_prop_changed) get_pos = getattr(device, '_get_{}'.format(axis_name)) set_pos = getattr(device, '_set_{}'.format(axis_name)) pos_text_widget.returnPressed.connect(submit_pos_text) pos_text_widget.focus_lost.connect(pos_text_focus_lost) update_high_limit = self.subscribe(high_limit_ppath, high_limit_prop_changed) if update_high_limit is None: raise TypeError( '{} is not a writable property!'.format(high_limit_ppath)) high_limit_text_widget.returnPressed.connect(submit_high_limit_text) high_limit_text_widget.focus_lost.connect(high_limit_text_focus_lost) reset_limits_button.clicked[bool].connect(reset_limits_button_clicked) # We do not receive events for z high soft limit changes initiated by means other than assigning # to scope.stage.z_high_soft_limit or calling scope.stage.reset_z_high_soft_limit(). However, # the scope's physical interface does not offer any way to modify z high soft limit, with one # possible exception: it would make sense for the limit to change with objective in order to prevent # head crashing. In case that happens, we refresh z high soft limit upon objective change. # TODO: verify that this is never needed and get rid of it if so if provoke_update_rppath is not None: def objective_changed(_): nonlocal handling_high_soft_limit_change if handling_high_soft_limit_change: return handling_high_soft_limit_change = True try: high_limit_text_widget.setText( str( self.pattr('{}.{}_high_soft_limit'.format( stage_rppath, axis_name)))) finally: handling_high_soft_limit_change = False self.subscribe(self.PROPERTY_ROOT + provoke_update_rppath, objective_changed) return widget