예제 #1
0
파일: AxisItem.py 프로젝트: robertsj/poropy
 def setScale(self, scale=None):
     """
     Set the value scaling for this axis. 
     The scaling value 1) multiplies the values displayed along the axis
     and 2) changes the way units are displayed in the label. 
     For example:
         If the axis spans values from -0.1 to 0.1 and has units set to 'V'
         then a scale of 1000 would cause the axis to display values -100 to 100
         and the units would appear as 'mV'
     If scale is None, then it will be determined automatically based on the current 
     range displayed by the axis.
     """
     if scale is None:
         #if self.drawLabel:  ## If there is a label, then we are free to rescale the values 
         if self.label.isVisible():
             d = self.range[1] - self.range[0]
             #(scale, prefix) = fn.siScale(d / 2.)
             (scale, prefix) = fn.siScale(max(abs(self.range[0]), abs(self.range[1])))
             if self.labelUnits == '' and prefix in ['k', 'm']:  ## If we are not showing units, wait until 1e6 before scaling.
                 scale = 1.0
                 prefix = ''
             self.setLabel(unitPrefix=prefix)
         else:
             scale = 1.0
     
     
     if scale != self.scale:
         self.scale = scale
         self.setLabel()
         self.picture = None
         self.update()
예제 #2
0
    def setScale(self, scale=None):
        """
        Set the value scaling for this axis. 
        The scaling value 1) multiplies the values displayed along the axis
        and 2) changes the way units are displayed in the label. 
        For example:
            If the axis spans values from -0.1 to 0.1 and has units set to 'V'
            then a scale of 1000 would cause the axis to display values -100 to 100
            and the units would appear as 'mV'
        If scale is None, then it will be determined automatically based on the current 
        range displayed by the axis.
        """
        if scale is None:
            # if self.drawLabel:  ## If there is a label, then we are free to rescale the values
            if self.label.isVisible():
                d = self.range[1] - self.range[0]
                # (scale, prefix) = fn.siScale(d / 2.)
                (scale, prefix) = fn.siScale(max(abs(self.range[0]), abs(self.range[1])))
                if self.labelUnits == "" and prefix in [
                    "k",
                    "m",
                ]:  ## If we are not showing units, wait until 1e6 before scaling.
                    scale = 1.0
                    prefix = ""
                self.setLabel(unitPrefix=prefix)
            else:
                scale = 1.0

        if scale != self.scale:
            self.scale = scale
            self.setLabel()
            self.picture = None
            self.update()
예제 #3
0
    def updateText(self, prev=None):
        
        # get the number of decimal places to print
        decimals = self.opts.get('decimals')        
        
        # temporarily disable validation
        self.skipValidate = True
        
        # if we're supposed to add a prefix to the label
        if self.opts['siPrefix']:
            
            # special case: if it's zero use the previous prefix
            if self.val == 0 and prev is not None:
                (s, p) = fn.siScale(prev)
                
                # NOTE: Could have the user specify a format string and use it here
                txt = ("%."+str(decimals)+"g %s%s") % (0, p, self.opts['suffix'])
            else:
                # NOTE: Could have the user specify a format string and send it as an argument here
                txt = fn.siFormat(float(self.val), precision=decimals, suffix=self.opts['suffix'])
 
        # otherwise, format the string manually
        else:
            # NOTE: Could have the user specify a format string and use it here
            txt = ('%.'+str(decimals)+'g%s') % (self.val , self.opts['suffix'])

        # actually set the text
        self.lineEdit().setText(txt)
        self.lastText = txt

        # re-enable the validation
        self.skipValidate = False
예제 #4
0
 def setFloat(self, val):
     """."""
     sc, prf = functions.siScale(val)
     val *= sc
     text = self.FMT.format(val)
     text += ' ' + prf + self.unit
     super().setText(text)
예제 #5
0
 def setScale(self, scale=None):
     """
     Set the value scaling for this axis. Values on the axis are multiplied
     by this scale factor before being displayed as text. By default,
     this scaling value is automatically determined based on the visible range
     and the axis units are updated to reflect the chosen scale factor.
     
     For example: If the axis spans values from -0.1 to 0.1 and has units set 
     to 'V' then a scale of 1000 would cause the axis to display values -100 to 100
     and the units would appear as 'mV'
     """
     if scale is None:
         #if self.drawLabel:  ## If there is a label, then we are free to rescale the values 
         if self.label.isVisible():
             #d = self.range[1] - self.range[0]
             #(scale, prefix) = fn.siScale(d / 2.)
             (scale, prefix) = fn.siScale(max(abs(self.range[0]), abs(self.range[1])))
             if self.labelUnits == '' and prefix in ['k', 'm']:  ## If we are not showing units, wait until 1e6 before scaling.
                 scale = 1.0
                 prefix = ''
             self.setLabel(unitPrefix=prefix)
         else:
             scale = 1.0
     else:
         self.setLabel(unitPrefix='')
         self.autoScale = False
         
     if scale != self.scale:
         self.scale = scale
         self.setLabel()
         self.picture = None
         self.update()
예제 #6
0
    def setScale(self, scale=None):
        """
        Set the value scaling for this axis. Values on the axis are multiplied
        by this scale factor before being displayed as text. By default,
        this scaling value is automatically determined based on the visible range
        and the axis units are updated to reflect the chosen scale factor.
        
        For example: If the axis spans values from -0.1 to 0.1 and has units set 
        to 'V' then a scale of 1000 would cause the axis to display values -100 to 100
        and the units would appear as 'mV'
        """
        if scale is None:
            #if self.drawLabel:  ## If there is a label, then we are free to rescale the values
            if self.label.isVisible():
                #d = self.range[1] - self.range[0]
                #(scale, prefix) = fn.siScale(d / 2.)
                (scale, prefix) = fn.siScale(
                    max(abs(self.range[0]), abs(self.range[1])))
                if self.labelUnits == '' and prefix in [
                        'k', 'm'
                ]:  ## If we are not showing units, wait until 1e6 before scaling.
                    scale = 1.0
                    prefix = ''
                self.setLabel(unitPrefix=prefix)
            else:
                scale = 1.0
        else:
            self.setLabel(unitPrefix='')
            self.autoScale = False

        if scale != self.scale:
            self.scale = scale
            self.setLabel()
            self.picture = None
            self.update()
예제 #7
0
    def _show_tooltip(self, pos, pln='x'):
        unit = 'rad'
        if self.is_orb:
            names = self._csorb.bpm_nicknames
            posi = self._csorb.bpm_pos
            unit = 'm'
        elif pln == 'x':
            names = self._csorb.ch_nicknames
            posi = self._csorb.ch_pos
        else:
            names = self._csorb.cv_nicknames
            posi = self._csorb.cv_pos

        graph = self.graph[pln]
        curve = graph.curveAtIndex(0)
        posx = curve.scatter.mapFromScene(pos).x()
        if self._csorb.isring:
            posx = posx % self._csorb.circum
        ind = _np.argmin(_np.abs(_np.array(posi) - posx))
        posy = curve.scatter.mapFromScene(pos).y()

        sca, prf = functions.siScale(posy)
        txt = '{0:s}, y = {1:.3f} {2:s}'.format(names[ind], sca * posy,
                                                prf + unit)
        QToolTip.showText(graph.mapToGlobal(pos.toPoint()), txt, graph,
                          graph.geometry(), 500)
예제 #8
0
파일: label.py 프로젝트: lnls-sirius/hla
    def value_changed(self, new_value):
        """
        Callback invoked when the Channel value is changed.
        Sets the value of new_value accordingly at the Label.

        Parameters
        ----------
        new_value : str, int, float, bool or np.ndarray
            The new value from the channel. The type depends on the channel.
        """
        super(SiriusLabel, self).value_changed(new_value)
        # If it is a DiaplayFormat.Time, parse with siriuspy.clientarch.Time
        if self._display_format_type == self.DisplayFormat.Time:
            time = _Time(int(new_value)).time().isoformat() \
                if new_value is not None else ''
            self.setText(time)
            return

        new_value = parse_value_for_display(
            value=new_value,
            precision=self.precision,
            display_format_type=self._display_format_type,
            string_encoding=self._string_encoding,
            widget=self)
        # If the value is a string, just display it as-is, no formatting
        # needed.
        if isinstance(new_value, str):
            self.setText(new_value)
            return
        # If the value is an enum, display the appropriate enum string for
        # the value.
        if self.enum_strings and isinstance(new_value, (int, float)):
            try:
                self.setText(self.enum_strings[int(new_value)])
            except IndexError:
                self.setText(f'Index Overflow [{new_value}]')
            return
        # If the value is a number (float or int), display it using a
        # format string if necessary.
        if isinstance(new_value, (int, float)):
            if self._show_units and self._unit != '':
                new_value *= self._conv
                sc, prf = func.siScale(new_value)
                self.setText(self.format_string.format(sc * new_value, prf))
            else:
                self.setText(self.format_string.format(new_value))
            return
        # If you made it this far, just turn whatever the heck the value
        # is into a string and display it.
        self.setText(str(new_value))
예제 #9
0
 def updateText(self, prev=None):
     #print "Update text."
     self.skipValidate = True
     if self.opts['siPrefix']:
         if self.val == 0 and prev is not None:
             (s, p) = fn.siScale(prev)
             txt = "0.0 %s%s" % (p, self.opts['suffix'])
         else:
             txt = fn.siFormat(float(self.val), suffix=self.opts['suffix'])
     else:
         txt = '%g%s' % (self.val , self.opts['suffix'])
     self.lineEdit().setText(txt)
     self.lastText = txt
     self.skipValidate = False
예제 #10
0
    def mouseMoveEvent(self, ev):
        unit = 'urad'
        pos = ev.pos()

        posx = self.curve.scatter.mapFromScene(pos).x()
        posx = posx % self.c0
        ind = _np.argmin(_np.abs(_np.array(self.xdata) - posx))
        posy = self.curve.scatter.mapFromScene(pos).y()

        sca, prf = functions.siScale(posy)
        txt = '{0:s}, y = {1:.3f} {2:s}'.format(self.tooltip_names[ind],
                                                sca * posy, prf + unit)
        QToolTip.showText(self.mapToGlobal(pos), txt, self, self.geometry(),
                          500)
예제 #11
0
 def updateText(self, prev=None):
     #print "Update text."
     self.skipValidate = True
     if self.opts['siPrefix']:
         if self.val == 0 and prev is not None:
             (s, p) = fn.siScale(prev)
             txt = "0.0 %s%s" % (p, self.opts['suffix'])
         else:
             txt = fn.siFormat(float(self.val), suffix=self.opts['suffix'])
     else:
         txt = '%g%s' % (self.val, self.opts['suffix'])
     self.lineEdit().setText(txt)
     self.lastText = txt
     self.skipValidate = False
예제 #12
0
    def updateAutoSIPrefix(self):
        if self.label.isVisible():
            (scale, prefix) = fn.siScale(max(abs(self.range[0] * self.scale), abs(self.range[1] * self.scale)))
            if self.labelUnits == "" and prefix in [
                "k",
                "m",
            ]:  ## If we are not showing units, wait until 1e6 before scaling.
                scale = 1.0
                prefix = ""
            self.setLabel(unitPrefix=prefix)
        else:
            scale = 1.0

        self.autoSIPrefixScale = scale
        self.picture = None
        self.update()
예제 #13
0
 def generateDrawSpecs(self, p):
     #Force proper SI prefix usage...
     # - this is normally done in self.updateAutoSIPrefix, but pyqtgraph
     #    only enables tick label scaling when there is a genuine x axis
     #    label (and we don't have one).
     scale = self.scale
     xmin, xmax = self.range
     (scale, prefix) = siScale(max(abs(xmin * scale), abs(xmax * scale)))
     self.autoSIPrefixScale = scale
     
     #Get the standard tick labels...
     ret = list(super(RTSAFrequencyAxisItem, self).generateDrawSpecs(p))
     
     #Add in our start/centre/stop labels...
     ret[2].extend(self._getFrequencyTextSpecs())
     
     return tuple(ret)
예제 #14
0
    def updateAutoSIPrefix(self):
        if self.label.isVisible():
            (scale, prefix) = fn.siScale(
                max(abs(self.range[0] * self.scale),
                    abs(self.range[1] * self.scale)))
            if self.labelUnits == '' and prefix in [
                    'k', 'm'
            ]:  ## If we are not showing units, wait until 1e6 before scaling.
                scale = 1.0
                prefix = ''
            self.setLabel(unitPrefix=prefix)
        else:
            scale = 1.0

        self.autoSIPrefixScale = scale
        self.picture = None
        self.update()
예제 #15
0
    def updateText(self, prev=None):
        # print("Update text.")

        self.skipValidate = True
        if self.opts['siPrefix']:
            if self.val == 0 and prev is not None:
                (s, p) = fn.siScale(prev)
                txt = "0.0 {0!s}{1!s}".format(p, self.opts['suffix'])
            else:
                txt = fn.siFormat(float(self.val),
                                  precision=self.opts['decimals'] + 1,
                                  suffix=self.opts['suffix'])
        else:
            txt = '{0:.14g}{1!s}'.format(self.val, self.opts['suffix'])
        self.lineEdit().setText(txt)
        self.lastText = txt
        self.skipValidate = False
예제 #16
0
    def updateText(self, prev=None):
        self.skipValidate = True
        if self.opts['siPrefix']:
            if self.val == 0 and prev is not None:
                (s, p) = fn.siScale(prev)
                txt = "0.0 %s%s" % (p, self.opts['suffix'])
            else:
                txt = fn.siFormat(float(self.val),
                                  precision=self.opts['decimals'],
                                  suffix=self.opts['suffix'])
        else:
            fmt = "%." + str(self.opts['decimals']) + "g%s"
            txt = fmt % (self.val, self.opts['suffix'])

        self.lineEdit().setText(txt)
        self.lastText = txt
        self.skipValidate = False
예제 #17
0
파일: SpinBox.py 프로젝트: a-stark/qudi
    def updateText(self, prev=None):
        # print("Update text.")

        self.skipValidate = True
        if self.opts['siPrefix']:
            if self.val == 0 and prev is not None:
                (s, p) = fn.siScale(prev)
                txt = "0.0 {0!s}{1!s}".format(p, self.opts['suffix'])
            else:
                txt = fn.siFormat(float(self.val),
                                  precision=self.opts['decimals']+1,
                                  suffix=self.opts['suffix']
                                  )
        else:
            txt = '{0:.14g}{1!s}'.format(self.val , self.opts['suffix'])
        self.lineEdit().setText(txt)
        self.lastText = txt
        self.skipValidate = False
예제 #18
0
 def _getFrequencyTextSpecs(self):
     """Generates f axis labels for the overridden generateDrawSpecs.
     
     Expected form is a list of (QRectF, Alignment flags, unicode).
     
     """
     #TODO: proper/consistent sig fig handling would be nice
     
     freq_min, freq_max = self.range
     freq_center = (freq_max + freq_min) / 2.0
     
     #baseline QRect values...
     # - note that we don't clip, and we do properly align, so (w,h) is meh
     y = 22
     w = 30
     h = 20
     
     #Use common SI scaling, based on the low end...
     # - this avoids start being 600.00 MHz, and stop being 2.50 GHz
     #scale, si_prefix = siScale(freq_min)
     
     #set label contents...
     Qt = QtCore.Qt #space saver
     xpositions = [0, 0.5* self.width(), self.width() - 50] # -50 is a hack to fix pyrf_plot clipping issue
     labels = ["Start", "Center", "Stop"]
     freqs = [freq_min, freq_center, freq_max]
     alignments = [Qt.AlignLeft, Qt.AlignCenter, Qt.AlignRight]
     
     #Generate labels in a common way...
     textSpecs = [] #same as in superclass generateDrawSpecs
     for label, freq, alignment, x in zip(labels, freqs, alignments, xpositions):
         rect = QtCore.QRectF(x, y, w, h)
         textFlags = alignment | Qt.TextDontClip | Qt.AlignVCenter
         if freq <= 0:
             freq_txt = u"---"
         else:
             scale, si_prefix = siScale(freq)
             freq_txt = u"%.4f %sHz" % ((scale * freq), si_prefix)
         txt = u"%s = %s" % (label, freq_txt)
         textSpecs.append((rect, textFlags, txt))
     
     return textSpecs
예제 #19
0
def create_formatted_output(param_dict, num_sig_digits=5):
    """ Display a parameter set nicely in SI units.

    @param dict param_dict: dictionary with entries being again dictionaries
                       with two needed keywords 'value' and 'unit' and one
                       optional keyword 'error'. Add the proper items to the
                       specified keywords.
                       Note, that if no error is specified, no proper
                       rounding (and therefore displaying) can be
                       guaranteed.

    @param int num_sig_digits: optional, the number of significant digits will
                               be taken, if the rounding procedure was not
                               successful at all. That will ensure at least that
                               not all the digits are displayed.
                               According to that the error will be displayed.

    @return str: a string, which is nicely formatted.

    Note:  If you want that the values are displayed in a certain order, then
           use OrderedDict from the collections package.
    Note2: The absolute tolerance to a zero is set to 1e-18.

    Example of a param dict:
        param_dict = {'Rabi frequency': {'value':123.43,   'error': 0.321,  'unit': 'Hz'},
                      'ODMR contrast':  {'value':2.563423, 'error': 0.523,  'unit': '%'},
                      'Fidelity':       {'value':0.783,    'error': 0.2222, 'unit': ''}}

        If you want to access on the value of the Fidelity, then you can do
        that via:
            param_dict['Fidelity']['value']
        or on the error of the ODMR contrast:
            param_dict['ODMR contrast']['error']


    """
    if (fn is None):
        raise Exception('This function requires pyqtgraph.')

    output_str = ''
    atol = 1e-18    # absolute tolerance for the detection of zero.

    for entry in param_dict:
        if param_dict[entry].get('error') is not None:

            value, error, digit = round_value_to_error(
                param_dict[entry]['value'], param_dict[entry]['error'])

            if (np.isclose(value, 0.0, atol=atol)
                    or np.isnan(error)
                    or np.isclose(error, 0.0, atol=atol)
                    or np.isinf(error)):
                sc_fact, unit_prefix = fn.siScale(param_dict[entry]['value'])
                str_val = '{0:.{1}e}'.format(
                    param_dict[entry]['value'], num_sig_digits - 1)
                if np.isnan(np.float(str_val)):
                    value = np.NAN
                elif np.isinf(np.float(str_val)):
                    value = np.inf
                else:
                    value = float('{0:.{1}e}'.format(
                        param_dict[entry]['value'], num_sig_digits - 1))

            else:
                # the factor 10 moves the displayed digit by one to the right,
                # so that the values from 100 to 0.1 are displayed within one
                # range, rather then from the value 1000 to 1, which is
                # default.
                sc_fact, unit_prefix = fn.siScale(error * 10)

            output_str += '{0}: {1} \u00B1 {2} {3}{4} \n'.format(entry,
                                                                 round(
                                                                     value * sc_fact, num_sig_digits - 1),
                                                                 round(
                                                                     error * sc_fact, num_sig_digits - 1),
                                                                 unit_prefix,
                                                                 param_dict[entry][
                                                                     'unit'],
                                                                 )

        else:
            output_str += '{0}: '.format(entry) + fn.siFormat(param_dict[entry]['value'],
                                                              precision=num_sig_digits,
                                                              suffix=param_dict[entry]['unit']) + '\n'

    return output_str
예제 #20
0
파일: SpinBox.py 프로젝트: a-stark/qudi
    def validate(self, strn, pos):
        # print('validate', strn, pos)
        if self.skipValidate:
            # print("skip validate")
            #self.textValid = False
            ret = QtGui.QValidator.Acceptable
        else:
            try:
                ## first make sure we didn't mess with the suffix
                suff = self.opts.get('suffix', '')

                # fix: if the whole text is selected and one needs to typ in a
                #      new number, then a single integer character is ignored.
                if len(strn) == 1 and strn.isdigit():
                    scl_str = fn.siScale(self.val)[1]
                    strn = '{0} {1}{2}'.format(strn, scl_str, suff)

                if len(suff) > 0 and asUnicode(strn)[-len(suff):] != suff:
                    #print '"%s" != "%s"' % (unicode(strn)[-len(suff):], suff)
                    ret = QtGui.QValidator.Invalid
                    # print('invalid input', 'suff:', suff, '{0} != {1}'.format(asUnicode(strn)[-len(suff):], suff))

                ## next see if we actually have an interpretable value
                else:
                    val = self.interpret()
                    if val is False:
                        #print "can't interpret"
                        #self.setStyleSheet('SpinBox {border: 2px solid #C55;}')
                        #self.textValid = False
                        ret = QtGui.QValidator.Intermediate
                    else:
                        if self.valueInRange(val):
                            if not self.opts['delayUntilEditFinished']:
                                self.setValue(val, update=False)
                            #print "  OK:", self.val
                            #self.setStyleSheet('')
                            #self.textValid = True

                            ret = QtGui.QValidator.Acceptable
                        else:
                            ret = QtGui.QValidator.Intermediate

            except:
                #print "  BAD"
                #import sys
                #sys.excepthook(*sys.exc_info())
                #self.textValid = False
                #self.setStyleSheet('SpinBox {border: 2px solid #C55;}')
                ret = QtGui.QValidator.Intermediate

        ## draw / clear border
        if ret == QtGui.QValidator.Intermediate:
            self.textValid = False
        elif ret == QtGui.QValidator.Acceptable:
            self.textValid = True
        ## note: if text is invalid, we don't change the textValid flag
        ## since the text will be forced to its previous state anyway
        self.update()

        ## support 2 different pyqt APIs. Bleh.
        if hasattr(QtCore, 'QString'):
            return (ret, pos)
        else:
            return (ret, strn, pos)
예제 #21
0
파일: units.py 프로젝트: a-stark/qudi
def create_formatted_output(param_dict, num_sig_digits=5):
    """ Display a parameter set nicely in SI units.

    @param dict param_dict: dictionary with entries being again dictionaries
                       with two needed keywords 'value' and 'unit' and one
                       optional keyword 'error'. Add the proper items to the
                       specified keywords.
                       Note, that if no error is specified, no proper
                       rounding (and therefore displaying) can be
                       guaranteed.

    @param int num_sig_digits: optional, the number of significant digits will
                               be taken, if the rounding procedure was not
                               successful at all. That will ensure at least that
                               not all the digits are displayed.
                               According to that the error will be displayed.

    @return str: a string, which is nicely formatted.

    Note:  If you want that the values are displayed in a certain order, then
           use OrderedDict from the collections package.
    Note2: The absolute tolerance to a zero is set to 1e-18.

    Example of a param dict:
        param_dict = {'Rabi frequency': {'value':123.43,   'error': 0.321,  'unit': 'Hz'},
                      'ODMR contrast':  {'value':2.563423, 'error': 0.523,  'unit': '%'},
                      'Fidelity':       {'value':0.783,    'error': 0.2222, 'unit': ''}}

        If you want to access on the value of the Fidelity, then you can do
        that via:
            param_dict['Fidelity']['value']
        or on the error of the ODMR contrast:
            param_dict['ODMR contrast']['error']


    """
    if (fn is None):
        raise Exception('This function requires pyqtgraph.')

    output_str = ''
    atol = 1e-18    # absolute tolerance for the detection of zero.

    for entry in param_dict:
        if param_dict[entry].get('error') is not None:

            value, error, digit = round_value_to_error(
                param_dict[entry]['value'], param_dict[entry]['error'])

            if (np.isclose(value, 0.0, atol=atol)
                    or np.isnan(error)
                    or np.isclose(error, 0.0, atol=atol)
                    or np.isinf(error)):
                sc_fact, unit_prefix = fn.siScale(param_dict[entry]['value'])
                str_val = '{0:.{1}e}'.format(
                    param_dict[entry]['value'], num_sig_digits - 1)
                if np.isnan(np.float(str_val)):
                    value = np.NAN
                elif np.isinf(np.float(str_val)):
                    value = np.inf
                else:
                    value = float('{0:.{1}e}'.format(
                        param_dict[entry]['value'], num_sig_digits - 1))

            else:
                # the factor 10 moves the displayed digit by one to the right,
                # so that the values from 100 to 0.1 are displayed within one
                # range, rather then from the value 1000 to 1, which is
                # default.
                sc_fact, unit_prefix = fn.siScale(error * 10)

            output_str += '{0}: {1} \u00B1 {2} {3}{4} \n'.format(entry,
                                                                 round(
                                                                     value * sc_fact, num_sig_digits - 1),
                                                                 round(
                                                                     error * sc_fact, num_sig_digits - 1),
                                                                 unit_prefix,
                                                                 param_dict[entry][
                                                                     'unit'],
                                                                 )

        else:
            output_str += '{0}: '.format(entry) + fn.siFormat(param_dict[entry]['value'],
                                                              precision=num_sig_digits,
                                                              suffix=param_dict[entry]['unit']) + '\n'

    return output_str
예제 #22
0
    def validate(self, strn, pos):
        # print('validate', strn, pos)
        if self.skipValidate:
            # print("skip validate")
            #self.textValid = False
            ret = QtGui.QValidator.Acceptable
        else:
            try:
                ## first make sure we didn't mess with the suffix
                suff = self.opts.get('suffix', '')

                # fix: if the whole text is selected and one needs to typ in a
                #      new number, then a single integer character is ignored.
                if len(strn) == 1 and strn.isdigit():
                    scl_str = fn.siScale(self.val)[1]
                    strn = '{0} {1}{2}'.format(strn, scl_str, suff)

                if len(suff) > 0 and asUnicode(strn)[-len(suff):] != suff:
                    #print '"%s" != "%s"' % (unicode(strn)[-len(suff):], suff)
                    ret = QtGui.QValidator.Invalid
                    # print('invalid input', 'suff:', suff, '{0} != {1}'.format(asUnicode(strn)[-len(suff):], suff))

                ## next see if we actually have an interpretable value
                else:
                    val = self.interpret()
                    if val is False:
                        #print "can't interpret"
                        #self.setStyleSheet('SpinBox {border: 2px solid #C55;}')
                        #self.textValid = False
                        ret = QtGui.QValidator.Intermediate
                    else:
                        if self.valueInRange(val):
                            if not self.opts['delayUntilEditFinished']:
                                self.setValue(val, update=False)
                            #print "  OK:", self.val
                            #self.setStyleSheet('')
                            #self.textValid = True

                            ret = QtGui.QValidator.Acceptable
                        else:
                            ret = QtGui.QValidator.Intermediate

            except:
                #print "  BAD"
                #import sys
                #sys.excepthook(*sys.exc_info())
                #self.textValid = False
                #self.setStyleSheet('SpinBox {border: 2px solid #C55;}')
                ret = QtGui.QValidator.Intermediate

        ## draw / clear border
        if ret == QtGui.QValidator.Intermediate:
            self.textValid = False
        elif ret == QtGui.QValidator.Acceptable:
            self.textValid = True
        ## note: if text is invalid, we don't change the textValid flag
        ## since the text will be forced to its previous state anyway
        self.update()

        ## support 2 different pyqt APIs. Bleh.
        if hasattr(QtCore, 'QString'):
            return (ret, pos)
        else:
            return (ret, strn, pos)