243 lines
9.1 KiB
Python
243 lines
9.1 KiB
Python
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
|