File: spinbox.py

package info (click to toggle)
orange3 3.40.0-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 15,908 kB
  • sloc: python: 162,745; ansic: 622; makefile: 322; sh: 93; cpp: 77
file content (150 lines) | stat: -rw-r--r-- 5,157 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import math
from decimal import Decimal

import numpy as np

from AnyQt.QtCore import QLocale, QSize
from AnyQt.QtWidgets import QDoubleSpinBox, QStyle, QStyleOptionSpinBox

DBL_MIN = float(np.finfo(float).min)
DBL_MAX = float(np.finfo(float).max)
DBL_MAX_10_EXP = math.floor(math.log10(DBL_MAX))
DBL_DIG = math.floor(math.log10(2 ** np.finfo(float).nmant))


class DoubleSpinBox(QDoubleSpinBox):
    """
    A QDoubleSpinSubclass with non-fixed decimal precision/rounding.
    """
    def __init__(self, parent=None, decimals=-1, minimumStep=1e-5,
                 minimumContentsLenght=-1, **kwargs):
        self.__decimals = decimals
        self.__minimumStep = minimumStep
        self.__minimumContentsLength = minimumContentsLenght
        stepType = kwargs.pop("stepType", DoubleSpinBox.DefaultStepType)
        super().__init__(parent, **kwargs)
        if decimals < 0:
            super().setDecimals(DBL_MAX_10_EXP + DBL_DIG)
        else:
            super().setDecimals(decimals)
        self.setStepType(stepType)

    def setDecimals(self, prec: int) -> None:
        """
        Set the number of decimals in display/edit

        If negative value then no rounding takes place and the value is
        displayed using `QLocale.FloatingPointShortest` precision.
        """
        self.__decimals = prec
        if prec < 0:
            # disable rounding in base implementation.
            super().setDecimals(DBL_MAX_10_EXP + DBL_DIG)
        else:
            super().setDecimals(prec)

    def decimals(self):
        return self.__decimals

    def setMinimumStep(self, step):
        """
        Minimum step size when `stepType() == AdaptiveDecimalStepType`
        and `decimals() < 0`.
        """
        self.__minimumStep = step

    def minimumStep(self):
        return self.__minimumStep

    def textFromValue(self, v: float) -> str:
        """Reimplemented."""
        if self.__decimals < 0:
            locale = self.locale()
            return locale.toString(v, 'f', QLocale.FloatingPointShortest)
        else:
            return super().textFromValue(v)

    def stepBy(self, steps: int) -> None:
        """
        Reimplemented.
        """
        # Compute up/down step using decimal type without rounding
        value = self.value()
        value_dec = Decimal(str(value))
        if self.stepType() == DoubleSpinBox.AdaptiveDecimalStepType:
            step_dec = self.__adaptiveDecimalStep(steps)
        else:
            step_dec = Decimal(str(self.singleStep()))
        # print(str(step_dec.fma(steps, value_dec)))
        value_dec = value_dec + step_dec * steps
        # print(str(value), "+", str(step_dec), "*", steps, "=", str(vinc))
        self.setValue(float(value_dec))

    def __adaptiveDecimalStep(self, steps: int) -> Decimal:
        # adapted from QDoubleSpinBoxPrivate::calculateAdaptiveDecimalStep
        decValue: Decimal = Decimal(str(self.value()))
        decimals = self.__decimals
        if decimals < 0:
            minStep = Decimal(str(self.__minimumStep))
        else:
            minStep = Decimal(10) ** -decimals

        absValue = abs(decValue)
        if absValue < minStep:
            return minStep
        valueNegative = decValue < 0
        stepsNegative = steps < 0
        if valueNegative != stepsNegative:
            absValue /= Decimal("1.01")
        step = Decimal(10) ** (math.floor(absValue.log10()) - 1)
        return max(minStep, step)

    if not hasattr(QDoubleSpinBox, "stepType"):  # pragma: no cover
        DefaultStepType = 0
        AdaptiveDecimalStepType = 1
        __stepType = AdaptiveDecimalStepType

        def setStepType(self, stepType):
            self.__stepType = stepType

        def stepType(self):
            return self.__stepType

    def setMinimumContentsLength(self, characters: int):
        self.__minimumContentsLength = characters
        self.updateGeometry()

    def minimumContentsLength(self):
        return self.__minimumContentsLength

    def sizeHint(self) -> QSize:
        if self.minimumContentsLength() < 0:
            return super().sizeHint()
        self.ensurePolished()
        fm = self.fontMetrics()
        template = "X" * self.minimumContentsLength()
        template += "."
        if self.prefix():
            template = self.prefix() + " " + template
        if self.suffix():
            template = template + self.suffix()
        if self.minimum() < 0.0:
            template = "-" + template
        if self.specialValueText():
            templates = [template, self.specialValueText()]
        else:
            templates = [template]
        height = self.lineEdit().sizeHint().height()
        width = max(map(fm.horizontalAdvance, templates))
        width += 2  # cursor blinking space
        hint = QSize(width, height)
        opt = QStyleOptionSpinBox()
        self.initStyleOption(opt)
        sh = self.style().sizeFromContents(QStyle.CT_SpinBox, opt, hint, self)
        return sh

    def minimumSizeHint(self) -> QSize:
        if self.minimumContentsLength() < 0:
            return super().minimumSizeHint()
        else:
            return self.sizeHint()