def __init__(self, parent=None, **kwargs): BaseEditor.__init__(self, parent, **kwargs) self.__method = DiscretizeEditor.EqualFreq self.__nintervals = 4 layout = QVBoxLayout() self.setLayout(layout) self.__group = group = QButtonGroup(self, exclusive=True) for method in [ self.EntropyMDL, self.EqualFreq, self.EqualWidth, self.Drop ]: rb = QRadioButton(self, text=self.Names[method], checked=self.__method == method) layout.addWidget(rb) group.addButton(rb, method) group.buttonClicked.connect(self.__on_buttonClicked) self.__slbox = slbox = QGroupBox( title="Number of intervals (for equal width/frequency)", flat=True) slbox.setLayout(QHBoxLayout()) self.__slider = slider = Slider(orientation=Qt.Horizontal, minimum=2, maximum=10, value=self.__nintervals, enabled=self.__method in [self.EqualFreq, self.EqualWidth], pageStep=1, tickPosition=QSlider.TicksBelow) slider.valueChanged.connect(self.__on_valueChanged) slbox.layout().addWidget(slider) self.__slabel = slabel = QLabel() slbox.layout().addWidget(slabel) container = QHBoxLayout() container.setContentsMargins(13, 0, 0, 0) container.addWidget(slbox) self.layout().insertLayout(3, container) self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
def __init__(self, *args, thresholds=(0.0, 1.0), center=None, **kwargs): super().__init__(*args, **kwargs) low = round(clip(thresholds[0], 0., 1.), 2) high = round(clip(thresholds[1], 0., 1.), 2) high = max(low, high) self.__threshold_low, self.__threshold_high = low, high self.__center = center form = QFormLayout(formAlignment=Qt.AlignLeft, labelAlignment=Qt.AlignLeft, fieldGrowthPolicy=QFormLayout.AllNonFixedFieldsGrow) form.setContentsMargins(0, 0, 0, 0) self.gradient_cb = QComboBox( None, objectName="gradient-combo-box", ) self.gradient_cb.setAttribute(Qt.WA_LayoutUsesWidgetRect) icsize = self.style().pixelMetric(QStyle.PM_SmallIconSize, None, self.gradient_cb) self.gradient_cb.setIconSize(QSize(64, icsize)) model = itemmodels.ContinuousPalettesModel() model.setParent(self) self.gradient_cb.setModel(model) self.gradient_cb.activated[int].connect(self.activated) self.gradient_cb.currentIndexChanged.connect(self.currentIndexChanged) if center is not None: def __on_center_changed(): self.__center = float(self.center_edit.text() or "0") self.centerChanged.emit(self.__center) self.center_box = QWidget() center_layout = QHBoxLayout() self.center_box.setLayout(center_layout) width = QFontMetrics(self.font()).boundingRect("9999999").width() self.center_edit = QLineEdit(text=f"{self.__center}", maximumWidth=width, placeholderText="0", alignment=Qt.AlignRight) self.center_edit.setValidator(QDoubleValidator()) self.center_edit.editingFinished.connect(__on_center_changed) center_layout.setContentsMargins(0, 0, 0, 0) center_layout.addStretch(1) center_layout.addWidget(QLabel("Centered at")) center_layout.addWidget(self.center_edit) self.gradient_cb.currentIndexChanged.connect( self.__update_center_visibility) else: self.center_box = None slider_low = Slider(objectName="threshold-low-slider", minimum=0, maximum=100, value=int(low * 100), orientation=Qt.Horizontal, tickPosition=QSlider.TicksBelow, pageStep=10, toolTip=self.tr("Low gradient threshold"), whatsThis=self.tr( "Applying a low threshold will squeeze the " "gradient from the lower end")) slider_high = Slider(objectName="threshold-low-slider", minimum=0, maximum=100, value=int(high * 100), orientation=Qt.Horizontal, tickPosition=QSlider.TicksAbove, pageStep=10, toolTip=self.tr("High gradient threshold"), whatsThis=self.tr( "Applying a high threshold will squeeze the " "gradient from the higher end")) form.setWidget(0, QFormLayout.SpanningRole, self.gradient_cb) if self.center_box: form.setWidget(1, QFormLayout.SpanningRole, self.center_box) form.addRow(self.tr("Low:"), slider_low) form.addRow(self.tr("High:"), slider_high) self.slider_low = slider_low self.slider_high = slider_high self.slider_low.valueChanged.connect(self.__on_slider_low_moved) self.slider_high.valueChanged.connect(self.__on_slider_high_moved) self.setLayout(form)
def __init__(self, *args, thresholds=(0.0, 1.0), center=None, **kwargs): super().__init__(*args, **kwargs) low = round(clip(thresholds[0], 0., 1.), 2) high = round(clip(thresholds[1], 0., 1.), 2) high = max(low, high) self.__threshold_low, self.__threshold_high = low, high self.__center = center form = QFormLayout( formAlignment=Qt.AlignLeft, labelAlignment=Qt.AlignLeft, fieldGrowthPolicy=QFormLayout.AllNonFixedFieldsGrow ) form.setContentsMargins(0, 0, 0, 0) self.gradient_cb = QComboBox( None, objectName="gradient-combo-box", ) self.gradient_cb.setAttribute(Qt.WA_LayoutUsesWidgetRect) icsize = self.style().pixelMetric( QStyle.PM_SmallIconSize, None, self.gradient_cb ) self.gradient_cb.setIconSize(QSize(64, icsize)) model = itemmodels.ContinuousPalettesModel() model.setParent(self) self.gradient_cb.setModel(model) self.gradient_cb.activated[int].connect(self.activated) self.gradient_cb.currentIndexChanged.connect(self.currentIndexChanged) if center is not None: def on_center_spin_value_changed(value): if self.__center != value: self.__center = value self.centerChanged.emit(self.__center) self.center_box = QWidget() center_layout = QHBoxLayout() self.center_box.setLayout(center_layout) self.center_edit = DoubleSpinBox( value=self.__center, minimum=DBL_MIN, maximum=DBL_MAX, minimumStep=0.01, minimumContentsLenght=8, stepType=DoubleSpinBox.AdaptiveDecimalStepType, keyboardTracking=False, sizePolicy=QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) ) self.center_edit.valueChanged.connect(on_center_spin_value_changed) center_layout.setContentsMargins(0, 0, 0, 0) center_layout.addStretch(1) center_layout.addWidget(QLabel("Centered at")) center_layout.addWidget(self.center_edit) self.gradient_cb.currentIndexChanged.connect( self.__update_center_visibility) else: self.center_box = None slider_low = Slider( objectName="threshold-low-slider", minimum=0, maximum=100, value=int(low * 100), orientation=Qt.Horizontal, tickPosition=QSlider.TicksBelow, pageStep=10, toolTip=self.tr("Low gradient threshold"), whatsThis=self.tr("Applying a low threshold will squeeze the " "gradient from the lower end") ) slider_high = Slider( objectName="threshold-low-slider", minimum=0, maximum=100, value=int(high * 100), orientation=Qt.Horizontal, tickPosition=QSlider.TicksAbove, pageStep=10, toolTip=self.tr("High gradient threshold"), whatsThis=self.tr("Applying a high threshold will squeeze the " "gradient from the higher end") ) form.setWidget(0, QFormLayout.SpanningRole, self.gradient_cb) if self.center_box: form.setWidget(1, QFormLayout.SpanningRole, self.center_box) form.addRow(self.tr("Low:"), slider_low) form.addRow(self.tr("High:"), slider_high) self.slider_low = slider_low self.slider_high = slider_high self.slider_low.valueChanged.connect(self.__on_slider_low_moved) self.slider_high.valueChanged.connect(self.__on_slider_high_moved) self.setLayout(form)
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 = Slider( 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)
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) def validate(self, text: str, pos: int) -> Tuple[int, str, int]: state, text, pos = super().validate(text, pos) if text == "": state = QValidator.Acceptable return state, text, pos def textFromValue(self, value): if not np.isfinite(value): return "?" return super().textFromValue(value) 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 = Slider( 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): value = self._spin.value() self.value = value if np.isfinite(value) else np.nan 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)