class ContinuousVariableEditor(VariableEditor): MAX_FLOAT = 2147483647 def __init__(self, parent: QWidget, variable: ContinuousVariable, min_value: float, max_value: float, callback: Callable): super().__init__(parent, callback) if np.isnan(min_value) or np.isnan(max_value): raise ValueError("Min/Max cannot be NaN.") n_decimals = variable.number_of_decimals abs_max = max(abs(min_value), max_value) if abs_max * 10**n_decimals > self.MAX_FLOAT: n_decimals = int(np.log10(self.MAX_FLOAT / abs_max)) self._value: float = min_value self._n_decimals: int = n_decimals self._min_value: float = self.__round_value(min_value) self._max_value: float = self.__round_value(max_value) sp_spin = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) sp_spin.setHorizontalStretch(1) sp_slider = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) sp_slider.setHorizontalStretch(5) sp_edit = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) sp_edit.setHorizontalStretch(1) class DoubleSpinBox(QDoubleSpinBox): def sizeHint(self) -> QSize: size: QSize = super().sizeHint() return QSize(size.width(), size.height() + 2) self._spin = DoubleSpinBox( parent, value=self._min_value, minimum=-np.inf, maximum=np.inf, singleStep=10**(-self._n_decimals), decimals=self._n_decimals, minimumWidth=70, sizePolicy=sp_spin, ) self._slider = QSlider( parent, minimum=self.__map_to_slider(self._min_value), maximum=self.__map_to_slider(self._max_value), singleStep=1, orientation=Qt.Horizontal, sizePolicy=sp_slider, ) self._label_min = QLabel( parent, text=variable.repr_val(min_value), alignment=Qt.AlignRight, minimumWidth=60, sizePolicy=sp_edit, ) self._label_max = QLabel( parent, text=variable.repr_val(max_value), alignment=Qt.AlignLeft, minimumWidth=60, sizePolicy=sp_edit, ) self._slider.valueChanged.connect(self._apply_slider_value) self._spin.valueChanged.connect(self._apply_spin_value) self.layout().addWidget(self._spin) self.layout().addWidget(self._label_min) self.layout().addWidget(self._slider) self.layout().addWidget(self._label_max) self.setFocusProxy(self._spin) def deselect(): self._spin.lineEdit().deselect() try: self._spin.lineEdit().selectionChanged.disconnect(deselect) except TypeError: pass # Invoking self.setFocusProxy(self._spin), causes the # self._spin.lineEdit()s to have selected texts (focus is set to # provide keyboard functionality, i.e.: pressing ESC after changing # spinbox value). Since the spin text is selected only after the # delegate draws it, it cannot be deselected during initialization. # Therefore connect the deselect() function to # self._spin.lineEdit().selectionChanged only for editor creation. self._spin.lineEdit().selectionChanged.connect(deselect) self._slider.installEventFilter(self) self._spin.installEventFilter(self) @property def value(self) -> float: return self.__round_value(self._value) @value.setter def value(self, value: float): if self._value is None or self.__round_value(value) != self.value: self._value = value self.valueChanged.emit(self.value) self._spin.setValue(self.value) # prevent emitting self.valueChanged again, due to slider change slider_value = self.__map_to_slider(self.value) self._value = self.__map_from_slider(slider_value) self._slider.setValue(slider_value) self._value = value def _apply_slider_value(self): self.value = self.__map_from_slider(self._slider.value()) def _apply_spin_value(self): self.value = self._spin.value() def __round_value(self, value): return round(value, self._n_decimals) def __map_to_slider(self, value: float) -> int: value = min(self._max_value, max(self._min_value, value)) return round(value * 10**self._n_decimals) def __map_from_slider(self, value: int) -> float: return value * 10**(-self._n_decimals) def eventFilter(self, obj: Union[QSlider, QDoubleSpinBox], event: QEvent) \ -> bool: if event.type() == QEvent.Wheel: return True return super().eventFilter(obj, event)
def init_form(self): # Get the current path of the file rootPath = os.path.dirname(__file__) vlayout = QVBoxLayout() hlayout = QHBoxLayout() if _api.USED_API == _api.QT_API_PYQT5: hlayout.setContentsMargins(0, 0, 0, 0) vlayout.setContentsMargins(0, 0, 0, 0) elif _api.USED_API == _api.QT_API_PYQT4: hlayout.setMargin(0) vlayout.setMargin(0) self.setLayout(vlayout) # Add scroll area scrollarea = QScrollArea() self._scrollArea = scrollarea scrollarea.setMinimumHeight(140) scrollarea.setWidgetResizable(True) scrollarea.keyPressEvent = self.__scrollAreaKeyPressEvent scrollarea.keyReleaseEvent = self.__scrollAreaKeyReleaseEvent vlayout.addWidget(scrollarea) # The timeline widget self._time = widget = TimelineWidget(self) widget._scroll = scrollarea scrollarea.setWidget(widget) # Timeline zoom slider slider = QSlider(QtCore.Qt.Horizontal) slider.setFocusPolicy(QtCore.Qt.NoFocus) slider.setMinimum(1) slider.setMaximum(100) slider.setValue(10) slider.setPageStep(1) slider.setTickPosition(QSlider.NoTicks) # TicksBothSides slider.valueChanged.connect(self.__scaleSliderChange) slider_label_zoom_in = QLabel() slider_label_zoom_out = QLabel() slider_label_zoom_in.setPixmap( conf.PYFORMS_PIXMAP_EVENTTIMELINE_ZOOM_IN) slider_label_zoom_out.setPixmap( conf.PYFORMS_PIXMAP_EVENTTIMELINE_ZOOM_OUT) self._zoomLabel = QLabel("100%") hlayout.addWidget(self._zoomLabel) hlayout.addWidget(slider_label_zoom_out) hlayout.addWidget(slider) hlayout.addWidget(slider_label_zoom_in) # Import/Export Buttons btn_import = QPushButton("Import") btn_import.setIcon(conf.PYFORMS_ICON_EVENTTIMELINE_IMPORT) btn_import.clicked.connect(self.__open_import_win_evt) btn_export = QPushButton("Export") btn_export.setIcon(conf.PYFORMS_ICON_EVENTTIMELINE_EXPORT) btn_export.clicked.connect(self.__export) hlayout.addWidget(btn_import) hlayout.addWidget(btn_export) vlayout.addLayout(hlayout)