import base64 from PySide6.QtCore import QPoint, QRect, Qt from PySide6.QtGui import QFont, QFontInfo, QIcon, QPainter, QPixmap from PySide6.QtSvg import QSvgRenderer from PySide6.QtWidgets import ( QComboBox, QGridLayout, QGroupBox, QHBoxLayout, QLabel, QPushButton, QScrollArea, QSizePolicy, QSlider, QSpacerItem, QVBoxLayout, QWidget, ) # _icon_color_from_palette: function definition for ScanSlicer behavior. def _icon_color_from_palette(palette): if palette is None: return "#f2f2f2" button_text_color = palette.buttonText().color() if button_text_color.isValid(): return button_text_color.name() window_color = palette.window().color() luma = ( (0.2126 * window_color.redF()) + (0.7152 * window_color.greenF()) + (0.0722 * window_color.blueF()) ) return "#1f1f1f" if luma >= 0.5 else "#f2f2f2" # icon_from_svg_data_url: function definition for ScanSlicer behavior. def icon_from_svg_data_url(data_url, size=24, color_hex=None, palette=None): prefix = "data:image/svg+xml;base64," if not data_url.startswith(prefix): return None try: svg_bytes = base64.b64decode(data_url[len(prefix):], validate=True) except Exception: return None if color_hex is None: color_hex = _icon_color_from_palette(palette) # Most toolbar SVGs use "currentColor"; replace it with the resolved color. if color_hex: try: svg_text = svg_bytes.decode("utf-8") svg_text = svg_text.replace("currentColor", color_hex) svg_text = svg_text.replace("currentcolor", color_hex) svg_bytes = svg_text.encode("utf-8") except UnicodeDecodeError: pass renderer = QSvgRenderer(svg_bytes) if not renderer.isValid(): return None pixmap = QPixmap(size, size) pixmap.fill(Qt.GlobalColor.transparent) painter = QPainter(pixmap) renderer.render(painter) painter.end() return QIcon(pixmap) # ScrollbarWheelOnlyScrollArea: class definition for ScanSlicer behavior. class ScrollbarWheelOnlyScrollArea(QScrollArea): # wheelEvent: function definition for ScanSlicer behavior. def wheelEvent(self, event): scrollbar = self.verticalScrollBar() if scrollbar is None or not scrollbar.isVisible(): event.ignore() return global_pos = event.globalPosition().toPoint() scrollbar_rect = QRect(scrollbar.mapToGlobal(QPoint(0, 0)), scrollbar.size()) if not scrollbar_rect.contains(global_pos): event.ignore() return super().wheelEvent(event) # ScanSlicerUiSharedMixin: class definition for ScanSlicer behavior. class ScanSlicerUiSharedMixin: # _build_section_title: function definition for ScanSlicer behavior. def _build_section_title(self, text): label = QLabel(text) title_font = QFont(label.font()) title_font.setPixelSize(self.SECTION_TITLE_FONT_PX) title_font.setBold(True) label.setFont(title_font) label.setStyleSheet( f"padding-top: {self.SECTION_TITLE_VPAD}px; " f"padding-bottom: {self.SECTION_TITLE_VPAD}px;" ) return label # _add_vertical_spacer: function definition for ScanSlicer behavior. def _add_vertical_spacer(self, target_layout, row_index): target_layout.addItem( QSpacerItem( 0, self.VERTICAL_SPACER_HEIGHT, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed, ), row_index, 0, 1, 2, ) # _new_group_layout: function definition for ScanSlicer behavior. def _new_group_layout(self): group = QGroupBox() group_layout = QGridLayout(group) group_layout.setContentsMargins( self.PANEL_CONTENT_MARGIN, self.PANEL_CONTENT_MARGIN, self.PANEL_CONTENT_MARGIN, self.PANEL_CONTENT_MARGIN, ) return group, group_layout # _add_full_width_widget: function definition for ScanSlicer behavior. def _add_full_width_widget(self, target_layout, row_index, widget): target_layout.addWidget(widget, row_index, 0, 1, 2) return row_index + 1 # _add_spacer_row: function definition for ScanSlicer behavior. def _add_spacer_row(self, target_layout, row_index): self._add_vertical_spacer(target_layout, row_index) return row_index + 1 # _add_label_row: function definition for ScanSlicer behavior. def _add_label_row(self, target_layout, row_index, text): return self._add_full_width_widget(target_layout, row_index, QLabel(text)) # _create_panel_button: function definition for ScanSlicer behavior. def _create_panel_button(self, text, callback, tooltip=None): button = QPushButton(text) if tooltip: button.setToolTip(tooltip) button.clicked.connect(callback) return button # _build_parameter_block: function definition for ScanSlicer behavior. def _build_parameter_block(self, parent, name, parameter): param_block = QWidget(parent) param_block_layout = QVBoxLayout(param_block) param_block_layout.setContentsMargins(0, 0, 0, self.VERTICAL_SPACER_HEIGHT) param_block_layout.setSpacing(3) param_label = QLabel(parameter.label) param_label.setWordWrap(True) param_block_layout.addWidget(param_label) if parameter.label_secondary: param_secondary_label = QLabel(parameter.label_secondary) param_secondary_label.setWordWrap(True) secondary_font = QFont(param_secondary_label.font()) secondary_info = QFontInfo(secondary_font) if secondary_info.pixelSize() > 0: secondary_font.setPixelSize(max(1, secondary_info.pixelSize() - 2)) elif secondary_info.pointSizeF() > 0: secondary_font.setPointSizeF(max(1.0, secondary_info.pointSizeF() - 1.0)) else: secondary_font.setPointSize(9) param_secondary_label.setFont(secondary_font) param_secondary_label.setStyleSheet("color: gray;") param_block_layout.addWidget(param_secondary_label) if name in self.CHOICE_PARAM_OPTIONS: control = QComboBox(param_block) for option_label, option_value in self.CHOICE_PARAM_OPTIONS[name]: control.addItem(option_label, option_value) choice_index = control.findData(parameter.get()) if choice_index >= 0: control.setCurrentIndex(choice_index) # on_param_choice_changed: function definition for ScanSlicer behavior. def on_param_choice_changed(index, p=parameter, combo=control, param_name=name): value = combo.itemData(index) p.set(value) if param_name in ("bw_method", "detect_pipeline", "morph_op"): self.update_parameter_visibility() if not self.updating_param_controls: self.set_preset_dropdown_custom() control.currentIndexChanged.connect(on_param_choice_changed) param_block_layout.addWidget(control) else: slider_row = QWidget(param_block) slider_row_layout = QHBoxLayout(slider_row) slider_row_layout.setContentsMargins(0, 0, 0, 0) slider_row_layout.setSpacing(6) control = QSlider(Qt.Orientation.Horizontal) control.setRange(parameter.min, parameter.max) control.setSingleStep(max(1, parameter.step)) control.setPageStep(max(1, parameter.step)) control.setTickInterval(max(1, parameter.step)) control.setValue(parameter.get()) value_label = QLabel(str(parameter.get())) value_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter) value_label.setMinimumWidth(40) # on_param_slider_changed: function definition for ScanSlicer behavior. def on_param_slider_changed(value, p=parameter, slider=control, label=value_label, param_name=name): step = max(1, p.step) snapped = p.min + round((value - p.min) / step) * step snapped = min(p.max, max(p.min, snapped)) if snapped != value: slider.blockSignals(True) slider.setValue(snapped) slider.blockSignals(False) value = snapped p.set(value) label.setText(str(p.get())) if param_name in ("bw_method", "detect_pipeline", "morph_op"): self.update_parameter_visibility() if not self.updating_param_controls: self.set_preset_dropdown_custom() control.valueChanged.connect(on_param_slider_changed) slider_row_layout.addWidget(control, 1) slider_row_layout.addWidget(value_label, 0) param_block_layout.addWidget(slider_row) self.param_controls[name] = control self.param_blocks[name] = param_block return param_block