Example #1
0
    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)
Example #2
0
    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)
Example #3
0
 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
Example #4
0
    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
Example #5
0
    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