ScanSlicer/scanslicer/ui_builders_shared.py

244 lines
9.1 KiB
Python
Raw Normal View History

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