590 lines
22 KiB
Python
590 lines
22 KiB
Python
"""Main PySide6 window emulating the legacy Tk interface."""
|
||
|
||
from __future__ import annotations
|
||
|
||
from pathlib import Path
|
||
from typing import Callable, Dict, List, Tuple
|
||
|
||
from PySide6 import QtCore, QtGui, QtWidgets
|
||
|
||
from app.logic import SUPPORTED_IMAGE_EXTENSIONS
|
||
from .image_processor import QtImageProcessor
|
||
|
||
DEFAULT_COLOUR = "#763e92"
|
||
PRESET_COLOURS: List[Tuple[str, str]] = [
|
||
("Red", "#ff3b30"),
|
||
("Orange", "#ff9500"),
|
||
("Yellow", "#ffd60a"),
|
||
("Green", "#34c759"),
|
||
("Teal", "#5ac8fa"),
|
||
("Blue", "#0a84ff"),
|
||
("Violet", "#af52de"),
|
||
("Magenta", "#ff2d55"),
|
||
("White", "#ffffff"),
|
||
("Grey", "#8e8e93"),
|
||
("Black", "#000000"),
|
||
]
|
||
|
||
SLIDER_SPECS: List[Tuple[str, str, int, int, int]] = [
|
||
("Hue min", "hue_min", 0, 360, 0),
|
||
("Hue max", "hue_max", 0, 360, 360),
|
||
("Sat min", "sat_min", 0, 100, 25),
|
||
("Value min", "val_min", 0, 100, 15),
|
||
("Value max", "val_max", 0, 100, 100),
|
||
("Overlay alpha", "alpha", 0, 255, 120),
|
||
]
|
||
|
||
|
||
class ToolbarButton(QtWidgets.QPushButton):
|
||
"""Rounded toolbar button inspired by the legacy design."""
|
||
|
||
def __init__(self, icon_text: str, label: str, callback: Callable[[], None], parent: QtWidgets.QWidget | None = None):
|
||
super().__init__(f"{icon_text} {label}", parent)
|
||
self.setCursor(QtCore.Qt.PointingHandCursor)
|
||
self.setFixedHeight(34)
|
||
self.setStyleSheet(
|
||
"""
|
||
QPushButton {
|
||
padding: 8px 16px;
|
||
border-radius: 10px;
|
||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||
background-color: rgba(255, 255, 255, 0.04);
|
||
color: #f7f7fb;
|
||
font-weight: 600;
|
||
}
|
||
QPushButton:hover {
|
||
background-color: rgba(255, 255, 255, 0.12);
|
||
}
|
||
QPushButton:pressed {
|
||
background-color: rgba(255, 255, 255, 0.18);
|
||
}
|
||
"""
|
||
)
|
||
self.clicked.connect(callback)
|
||
|
||
|
||
class ColourSwatch(QtWidgets.QPushButton):
|
||
"""Clickable palette swatch."""
|
||
|
||
def __init__(self, name: str, hex_code: str, callback: Callable[[str, str], None], parent: QtWidgets.QWidget | None = None):
|
||
super().__init__(parent)
|
||
self.hex_code = hex_code
|
||
self.name = name
|
||
self.setToolTip(f"{name} ({hex_code})")
|
||
self.setCursor(QtCore.Qt.PointingHandCursor)
|
||
self.setFixedSize(28, 28)
|
||
self.setStyleSheet(
|
||
f"""
|
||
QPushButton {{
|
||
background-color: {hex_code};
|
||
border: 2px solid rgba(255, 255, 255, 0.18);
|
||
border-radius: 6px;
|
||
}}
|
||
QPushButton:hover {{
|
||
border-color: rgba(255, 255, 255, 0.45);
|
||
}}
|
||
"""
|
||
)
|
||
self.clicked.connect(lambda: callback(hex_code, name))
|
||
|
||
|
||
class SliderControl(QtWidgets.QWidget):
|
||
"""Slider with header and live value label."""
|
||
|
||
value_changed = QtCore.Signal(str, int)
|
||
|
||
def __init__(self, title: str, key: str, minimum: int, maximum: int, initial: int):
|
||
super().__init__()
|
||
self.key = key
|
||
|
||
layout = QtWidgets.QVBoxLayout(self)
|
||
layout.setContentsMargins(0, 0, 0, 0)
|
||
layout.setSpacing(4)
|
||
|
||
header = QtWidgets.QHBoxLayout()
|
||
header.setContentsMargins(0, 0, 0, 0)
|
||
title_label = QtWidgets.QLabel(title)
|
||
title_label.setStyleSheet("color: rgba(255,255,255,0.85); font-weight: 500;")
|
||
header.addWidget(title_label)
|
||
header.addStretch(1)
|
||
self.value_label = QtWidgets.QLabel(str(initial))
|
||
self.value_label.setStyleSheet("color: rgba(255,255,255,0.6);")
|
||
header.addWidget(self.value_label)
|
||
layout.addLayout(header)
|
||
|
||
self.slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
|
||
self.slider.setRange(minimum, maximum)
|
||
self.slider.setValue(initial)
|
||
self.slider.setCursor(QtCore.Qt.PointingHandCursor)
|
||
self.slider.setStyleSheet(
|
||
"""
|
||
QSlider::groove:horizontal {
|
||
border: 1px solid rgba(255,255,255,0.08);
|
||
height: 6px;
|
||
background: rgba(255,255,255,0.1);
|
||
border-radius: 4px;
|
||
}
|
||
QSlider::handle:horizontal {
|
||
background: #9a4dff;
|
||
border: 1px solid rgba(255,255,255,0.2);
|
||
width: 14px;
|
||
margin: -5px 0;
|
||
border-radius: 7px;
|
||
}
|
||
"""
|
||
)
|
||
self.slider.valueChanged.connect(self._sync_value)
|
||
layout.addWidget(self.slider)
|
||
|
||
def _sync_value(self, value: int) -> None:
|
||
self.value_label.setText(str(value))
|
||
self.value_changed.emit(self.key, value)
|
||
|
||
def set_value(self, value: int) -> None:
|
||
self.slider.blockSignals(True)
|
||
self.slider.setValue(value)
|
||
self.slider.blockSignals(False)
|
||
self.value_label.setText(str(value))
|
||
|
||
|
||
class ImageView(QtWidgets.QLabel):
|
||
"""Aspect-ratio aware image view."""
|
||
|
||
def __init__(self) -> None:
|
||
super().__init__()
|
||
self.setAlignment(QtCore.Qt.AlignCenter)
|
||
self._pixmap: QtGui.QPixmap | None = None
|
||
|
||
def set_image(self, pixmap: QtGui.QPixmap | None) -> None:
|
||
self._pixmap = pixmap
|
||
self._rescale()
|
||
|
||
def resizeEvent(self, event: QtGui.QResizeEvent) -> None:
|
||
super().resizeEvent(event)
|
||
self._rescale()
|
||
|
||
def _rescale(self) -> None:
|
||
if self._pixmap is None:
|
||
self.setPixmap(QtGui.QPixmap())
|
||
self.setText("<No image loaded>")
|
||
self.setStyleSheet("color: rgba(255,255,255,0.45); font-size: 14px;")
|
||
return
|
||
target = self._pixmap.scaled(
|
||
self.size(),
|
||
QtCore.Qt.KeepAspectRatio,
|
||
QtCore.Qt.SmoothTransformation,
|
||
)
|
||
self.setPixmap(target)
|
||
self.setStyleSheet("")
|
||
|
||
|
||
class TitleBar(QtWidgets.QWidget):
|
||
"""Custom title bar with native window controls."""
|
||
|
||
HEIGHT = 40
|
||
|
||
def __init__(self, window: "MainWindow") -> None:
|
||
super().__init__(window)
|
||
self.window = window
|
||
self.setFixedHeight(self.HEIGHT)
|
||
self.setCursor(QtCore.Qt.ArrowCursor)
|
||
self.setAutoFillBackground(True)
|
||
|
||
palette = self.palette()
|
||
palette.setColor(QtGui.QPalette.Window, QtGui.QColor("#16171d"))
|
||
self.setPalette(palette)
|
||
|
||
layout = QtWidgets.QHBoxLayout(self)
|
||
layout.setContentsMargins(12, 8, 12, 8)
|
||
layout.setSpacing(8)
|
||
|
||
logo_path = Path(__file__).resolve().parents[1] / "assets" / "logo.png"
|
||
if logo_path.exists():
|
||
pixmap = QtGui.QPixmap(str(logo_path))
|
||
logo_label = QtWidgets.QLabel()
|
||
logo_label.setPixmap(pixmap.scaled(26, 26, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation))
|
||
layout.addWidget(logo_label)
|
||
|
||
title_label = QtWidgets.QLabel("Interactive Color Range Analyzer")
|
||
title_label.setStyleSheet("color: #f7f7fb; font-weight: 600;")
|
||
layout.addWidget(title_label)
|
||
layout.addStretch(1)
|
||
|
||
self.min_btn = self._create_button("–", "Minimise window")
|
||
self.min_btn.clicked.connect(window.showMinimized)
|
||
layout.addWidget(self.min_btn)
|
||
|
||
self.max_btn = self._create_button("❐", "Maximise / Restore")
|
||
self.max_btn.clicked.connect(window.toggle_maximise)
|
||
layout.addWidget(self.max_btn)
|
||
|
||
close_btn = self._create_button("✕", "Close")
|
||
close_btn.clicked.connect(window.close)
|
||
close_btn.setStyleSheet(
|
||
"""
|
||
QPushButton { background: transparent; color: #f7f7fb; border: none; padding: 4px 10px; }
|
||
QPushButton:hover { background-color: #d0342c; }
|
||
"""
|
||
)
|
||
layout.addWidget(close_btn)
|
||
|
||
def _create_button(self, text: str, tooltip: str) -> QtWidgets.QPushButton:
|
||
btn = QtWidgets.QPushButton(text)
|
||
btn.setToolTip(tooltip)
|
||
btn.setFixedSize(36, 24)
|
||
btn.setCursor(QtCore.Qt.ArrowCursor)
|
||
btn.setStyleSheet(
|
||
"""
|
||
QPushButton {
|
||
background-color: transparent;
|
||
color: #f7f7fb;
|
||
border: none;
|
||
padding: 4px 10px;
|
||
}
|
||
QPushButton:hover {
|
||
background-color: rgba(255, 255, 255, 0.1);
|
||
}
|
||
"""
|
||
)
|
||
return btn
|
||
|
||
def mouseDoubleClickEvent(self, event: QtGui.QMouseEvent) -> None:
|
||
if event.button() == QtCore.Qt.LeftButton:
|
||
self.window.toggle_maximise()
|
||
event.accept()
|
||
|
||
def mousePressEvent(self, event: QtGui.QMouseEvent) -> None:
|
||
if event.button() == QtCore.Qt.LeftButton:
|
||
self.window.start_system_move(event.globalPosition())
|
||
event.accept()
|
||
super().mousePressEvent(event)
|
||
|
||
|
||
class MainWindow(QtWidgets.QMainWindow):
|
||
"""Main application window containing all controls."""
|
||
|
||
def __init__(self) -> None:
|
||
super().__init__()
|
||
self.setWindowFlag(QtCore.Qt.FramelessWindowHint)
|
||
self.setWindowFlag(QtCore.Qt.Window)
|
||
self.setAttribute(QtCore.Qt.WA_TranslucentBackground, False)
|
||
self.setMinimumSize(1100, 680)
|
||
|
||
container = QtWidgets.QWidget()
|
||
container_layout = QtWidgets.QVBoxLayout(container)
|
||
container_layout.setContentsMargins(0, 0, 0, 0)
|
||
container_layout.setSpacing(0)
|
||
|
||
self.title_bar = TitleBar(self)
|
||
container_layout.addWidget(self.title_bar)
|
||
|
||
self.content = QtWidgets.QWidget()
|
||
self.content.setStyleSheet("background-color: #111216;")
|
||
content_layout = QtWidgets.QVBoxLayout(self.content)
|
||
content_layout.setContentsMargins(24, 24, 24, 24)
|
||
content_layout.setSpacing(18)
|
||
|
||
content_layout.addLayout(self._build_toolbar())
|
||
content_layout.addLayout(self._build_palette())
|
||
content_layout.addLayout(self._build_sliders())
|
||
content_layout.addWidget(self._build_previews(), 1)
|
||
content_layout.addLayout(self._build_status_section())
|
||
|
||
container_layout.addWidget(self.content, 1)
|
||
self.setCentralWidget(container)
|
||
|
||
self._is_maximised = False
|
||
self._current_image_path: Path | None = None
|
||
self._current_colour = DEFAULT_COLOUR
|
||
self._toolbar_actions: Dict[str, Callable[[], None]] = {}
|
||
self._slider_controls: Dict[str, SliderControl] = {}
|
||
self._register_default_actions()
|
||
self._update_colour_display(DEFAULT_COLOUR, "Default colour")
|
||
|
||
self.processor = QtImageProcessor()
|
||
|
||
# Window control helpers -------------------------------------------------
|
||
|
||
def toggle_maximise(self) -> None:
|
||
handle = self.windowHandle()
|
||
if handle is None:
|
||
return
|
||
if self._is_maximised:
|
||
self.showNormal()
|
||
self._is_maximised = False
|
||
self.title_bar.max_btn.setText("❐")
|
||
else:
|
||
self.showMaximized()
|
||
self._is_maximised = True
|
||
self.title_bar.max_btn.setText("⧉")
|
||
|
||
def start_system_move(self, _global_position: QtCore.QPointF) -> None:
|
||
handle = self.windowHandle()
|
||
if handle:
|
||
handle.startSystemMove()
|
||
|
||
# UI builders ------------------------------------------------------------
|
||
|
||
def _build_toolbar(self) -> QtWidgets.QHBoxLayout:
|
||
layout = QtWidgets.QHBoxLayout()
|
||
layout.setSpacing(12)
|
||
|
||
buttons = [
|
||
("open_image", "🖼", "Open image…"),
|
||
("open_folder", "📂", "Open folder…"),
|
||
("choose_color", "🎨", "Choose colour"),
|
||
("pick_from_image", "🖱", "Pick from image"),
|
||
("save_overlay", "💾", "Save overlay"),
|
||
("toggle_free_draw", "△", "Toggle free-draw"),
|
||
("clear_excludes", "🧹", "Clear exclusions"),
|
||
("undo_exclude", "↩", "Undo last exclusion"),
|
||
("reset_sliders", "🔄", "Reset sliders"),
|
||
("toggle_theme", "🌓", "Toggle theme"),
|
||
]
|
||
self._toolbar_buttons: Dict[str, ToolbarButton] = {}
|
||
for key, icon_txt, label in buttons:
|
||
button = ToolbarButton(icon_txt, label, lambda k=key: self._invoke_action(k))
|
||
layout.addWidget(button)
|
||
self._toolbar_buttons[key] = button
|
||
|
||
layout.addStretch(1)
|
||
self.status_label = QtWidgets.QLabel("No image loaded")
|
||
self.status_label.setStyleSheet("color: rgba(255,255,255,0.7); font-weight: 500;")
|
||
layout.addWidget(self.status_label, 0, QtCore.Qt.AlignRight)
|
||
return layout
|
||
|
||
def _build_palette(self) -> QtWidgets.QHBoxLayout:
|
||
layout = QtWidgets.QHBoxLayout()
|
||
layout.setContentsMargins(0, 0, 0, 0)
|
||
layout.setSpacing(16)
|
||
|
||
current_group = QtWidgets.QHBoxLayout()
|
||
current_group.setSpacing(8)
|
||
current_label = QtWidgets.QLabel("Current colour")
|
||
current_label.setStyleSheet("color: rgba(255,255,255,0.75); font-weight: 500;")
|
||
current_group.addWidget(current_label)
|
||
|
||
self.current_colour_swatch = QtWidgets.QLabel()
|
||
self.current_colour_swatch.setFixedSize(28, 28)
|
||
self.current_colour_swatch.setStyleSheet(f"background-color: {DEFAULT_COLOUR}; border-radius: 6px;")
|
||
current_group.addWidget(self.current_colour_swatch)
|
||
|
||
self.current_colour_label = QtWidgets.QLabel(f"({DEFAULT_COLOUR})")
|
||
self.current_colour_label.setStyleSheet("color: rgba(255,255,255,0.65);")
|
||
current_group.addWidget(self.current_colour_label)
|
||
layout.addLayout(current_group)
|
||
|
||
more_label = QtWidgets.QLabel("More colours")
|
||
more_label.setStyleSheet("color: rgba(255,255,255,0.65); font-weight: 500;")
|
||
layout.addWidget(more_label)
|
||
|
||
swatch_container = QtWidgets.QHBoxLayout()
|
||
swatch_container.setSpacing(8)
|
||
for name, hex_code in PRESET_COLOURS:
|
||
swatch = ColourSwatch(name, hex_code, self._update_colour_display)
|
||
swatch_container.addWidget(swatch)
|
||
layout.addLayout(swatch_container)
|
||
layout.addStretch(1)
|
||
return layout
|
||
|
||
def _build_sliders(self) -> QtWidgets.QHBoxLayout:
|
||
layout = QtWidgets.QHBoxLayout()
|
||
layout.setSpacing(16)
|
||
self._slider_controls.clear()
|
||
for title, key, minimum, maximum, initial in SLIDER_SPECS:
|
||
control = SliderControl(title, key, minimum, maximum, initial)
|
||
control.value_changed.connect(self._on_slider_change)
|
||
layout.addWidget(control)
|
||
self._slider_controls[key] = control
|
||
return layout
|
||
|
||
def _build_previews(self) -> QtWidgets.QWidget:
|
||
container = QtWidgets.QWidget()
|
||
layout = QtWidgets.QGridLayout(container)
|
||
layout.setContentsMargins(0, 0, 0, 0)
|
||
layout.setHorizontalSpacing(16)
|
||
layout.setVerticalSpacing(0)
|
||
|
||
self.prev_button = QtWidgets.QToolButton()
|
||
self.prev_button.setText("◀")
|
||
self.prev_button.setCursor(QtCore.Qt.PointingHandCursor)
|
||
self.prev_button.setFixedSize(44, 44)
|
||
self.prev_button.setStyleSheet(
|
||
"""
|
||
QToolButton {
|
||
border-radius: 22px;
|
||
background-color: rgba(255, 255, 255, 0.08);
|
||
color: #f7f7fb;
|
||
font-size: 18px;
|
||
}
|
||
QToolButton:hover {
|
||
background-color: rgba(255, 255, 255, 0.16);
|
||
}
|
||
"""
|
||
)
|
||
self.prev_button.clicked.connect(lambda: self._invoke_action("show_previous_image"))
|
||
layout.addWidget(self.prev_button, 0, 0, QtCore.Qt.AlignVCenter)
|
||
|
||
self.image_view = ImageView()
|
||
self.image_view.setStyleSheet("border: 1px solid rgba(255,255,255,0.08); border-radius: 12px;")
|
||
layout.addWidget(self.image_view, 0, 1)
|
||
|
||
self.overlay_view = ImageView()
|
||
self.overlay_view.setStyleSheet("border: 1px solid rgba(255,255,255,0.08); border-radius: 12px;")
|
||
layout.addWidget(self.overlay_view, 0, 2)
|
||
|
||
self.next_button = QtWidgets.QToolButton()
|
||
self.next_button.setText("▶")
|
||
self.next_button.setCursor(QtCore.Qt.PointingHandCursor)
|
||
self.next_button.setFixedSize(44, 44)
|
||
self.next_button.setStyleSheet(self.prev_button.styleSheet())
|
||
self.next_button.clicked.connect(lambda: self._invoke_action("show_next_image"))
|
||
layout.addWidget(self.next_button, 0, 3, QtCore.Qt.AlignVCenter)
|
||
layout.setColumnStretch(1, 1)
|
||
layout.setColumnStretch(2, 1)
|
||
return container
|
||
|
||
def _build_status_section(self) -> QtWidgets.QVBoxLayout:
|
||
layout = QtWidgets.QVBoxLayout()
|
||
layout.setSpacing(8)
|
||
layout.setContentsMargins(0, 0, 0, 0)
|
||
|
||
self.filename_label = QtWidgets.QLabel("—")
|
||
self.filename_label.setAlignment(QtCore.Qt.AlignCenter)
|
||
self.filename_label.setStyleSheet("color: rgba(255,255,255,0.85); font-weight: 600;")
|
||
layout.addWidget(self.filename_label)
|
||
|
||
self.ratio_label = QtWidgets.QLabel("Matches with exclusions: —")
|
||
self.ratio_label.setAlignment(QtCore.Qt.AlignCenter)
|
||
self.ratio_label.setStyleSheet("color: #e6b84b; font-weight: 600;")
|
||
layout.addWidget(self.ratio_label)
|
||
return layout
|
||
|
||
# Action wiring ----------------------------------------------------------
|
||
|
||
def _register_default_actions(self) -> None:
|
||
self._toolbar_actions = {
|
||
"open_image": self.open_image,
|
||
"open_folder": self.open_folder,
|
||
"choose_color": self._coming_soon,
|
||
"pick_from_image": self._coming_soon,
|
||
"save_overlay": self._coming_soon,
|
||
"toggle_free_draw": self._coming_soon,
|
||
"clear_excludes": self._coming_soon,
|
||
"undo_exclude": self._coming_soon,
|
||
"reset_sliders": self._reset_sliders,
|
||
"toggle_theme": self._coming_soon,
|
||
"show_previous_image": self.show_previous_image,
|
||
"show_next_image": self.show_next_image,
|
||
}
|
||
|
||
def _invoke_action(self, key: str) -> None:
|
||
action = self._toolbar_actions.get(key)
|
||
if action:
|
||
action()
|
||
|
||
# Image handling ---------------------------------------------------------
|
||
|
||
def open_image(self) -> None:
|
||
filters = "Images (*.png *.jpg *.jpeg *.bmp *.webp)"
|
||
path_str, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Select image", "", filters)
|
||
if not path_str:
|
||
return
|
||
path = Path(path_str)
|
||
try:
|
||
self.processor.load_single_image(path)
|
||
except Exception as exc:
|
||
QtWidgets.QMessageBox.warning(self, "ICRA", f"Unable to open the selected image.\n{exc}")
|
||
return
|
||
self._current_image_path = path
|
||
self._refresh_views()
|
||
|
||
def open_folder(self) -> None:
|
||
directory = QtWidgets.QFileDialog.getExistingDirectory(self, "Select image folder")
|
||
if not directory:
|
||
return
|
||
folder = Path(directory)
|
||
paths = sorted(
|
||
(p for p in folder.iterdir() if p.suffix.lower() in SUPPORTED_IMAGE_EXTENSIONS and p.is_file()),
|
||
key=lambda p: p.name.lower(),
|
||
)
|
||
if not paths:
|
||
QtWidgets.QMessageBox.information(self, "ICRA", "No supported image files found in the selected folder.")
|
||
return
|
||
try:
|
||
self.processor.load_folder(paths)
|
||
except ValueError as exc:
|
||
QtWidgets.QMessageBox.information(self, "ICRA", str(exc))
|
||
return
|
||
self._current_image_path = paths[0]
|
||
self._refresh_views()
|
||
|
||
def show_previous_image(self) -> None:
|
||
if not self.processor.preview_paths:
|
||
self._coming_soon()
|
||
return
|
||
self.processor.previous_image()
|
||
self._current_image_path = self.processor.preview_paths[self.processor.current_index]
|
||
self._refresh_views()
|
||
|
||
def show_next_image(self) -> None:
|
||
if not self.processor.preview_paths:
|
||
self._coming_soon()
|
||
return
|
||
self.processor.next_image()
|
||
self._current_image_path = self.processor.preview_paths[self.processor.current_index]
|
||
self._refresh_views()
|
||
|
||
# Helpers ----------------------------------------------------------------
|
||
|
||
def _update_colour_display(self, hex_code: str, label: str) -> None:
|
||
self._current_colour = hex_code
|
||
self.current_colour_swatch.setStyleSheet(f"background-color: {hex_code}; border-radius: 6px;")
|
||
self.current_colour_label.setText(f"({hex_code})")
|
||
self.status_label.setText(f"{label}: {hex_code}")
|
||
|
||
def _on_slider_change(self, key: str, value: int) -> None:
|
||
formatted = key.replace("_", " ").title()
|
||
self.processor.set_threshold(key, value)
|
||
self.status_label.setText(f"{formatted} → {value}")
|
||
self._refresh_overlay_only()
|
||
|
||
def _reset_sliders(self) -> None:
|
||
for _, key, _, _, initial in SLIDER_SPECS:
|
||
control = self._slider_controls.get(key)
|
||
if control:
|
||
control.set_value(initial)
|
||
self.processor.set_threshold(key, initial)
|
||
self.status_label.setText("Sliders reset to defaults")
|
||
self._refresh_overlay_only()
|
||
|
||
def _coming_soon(self) -> None:
|
||
QtWidgets.QMessageBox.information(
|
||
self,
|
||
"Coming soon",
|
||
"This action is not yet wired up in the PySide6 migration prototype.",
|
||
)
|
||
|
||
# view refresh -----------------------------------------------------------
|
||
|
||
def _refresh_views(self) -> None:
|
||
preview_pix = self.processor.preview_pixmap()
|
||
overlay_pix = self.processor.overlay_pixmap()
|
||
self.image_view.set_image(preview_pix)
|
||
self.overlay_view.set_image(overlay_pix)
|
||
if self._current_image_path and self.processor.preview_img:
|
||
width, height = self.processor.preview_img.size
|
||
self.status_label.setText(
|
||
f"{self._current_image_path.name} · {width}×{height}"
|
||
)
|
||
self.filename_label.setText(
|
||
f"{self._current_image_path.name} ({width}×{height})"
|
||
)
|
||
self.ratio_label.setText(self.processor.stats.summary())
|
||
|
||
def _refresh_overlay_only(self) -> None:
|
||
if self.processor.preview_img is None:
|
||
return
|
||
self.overlay_view.set_image(self.processor.overlay_pixmap())
|
||
self.ratio_label.setText(self.processor.stats.summary())
|