Fixes mirrored slice exports by making orientation explicit: - PhotoSlice.swapped flag persists a user left/right swap - swap_left_right() mirrors [TL,TR,BR,BL] -> [TR,TL,BL,BR] - normalize_top_left() honors the flag so squarize/reset_rotation no longer overwrite manual orientation - Swap L/R toolbar button wired through canvas state + controller - Color-code left (orange) vs right (cyan) corners and edges
199 lines
7.7 KiB
Python
199 lines
7.7 KiB
Python
from PySide6.QtCore import QSize, Qt
|
|
from PySide6.QtWidgets import QGridLayout, QHBoxLayout, QLabel, QPushButton, QWidget
|
|
|
|
from .canvas import QtSlicingCanvas
|
|
from .ui_builders_shared import ScanSlicerUiSharedMixin, icon_from_svg_data_url
|
|
|
|
|
|
# ScanSlicerUiPreviewMixin: class definition for ScanSlicer behavior.
|
|
class ScanSlicerUiPreviewMixin(ScanSlicerUiSharedMixin):
|
|
# _create_toolbar_button: function definition for ScanSlicer behavior.
|
|
def _create_toolbar_button(
|
|
self,
|
|
parent,
|
|
text,
|
|
tooltip,
|
|
icon_data_url,
|
|
callback,
|
|
checkable=False,
|
|
enabled=True,
|
|
use_toggled=False,
|
|
):
|
|
button = QPushButton(text, parent)
|
|
if checkable:
|
|
button.setCheckable(True)
|
|
button.setEnabled(enabled)
|
|
button.setToolTip(tooltip)
|
|
icon = icon_from_svg_data_url(icon_data_url, 20, palette=button.palette())
|
|
if icon is not None:
|
|
button.setText("")
|
|
button.setIcon(icon)
|
|
button.setIconSize(QSize(20, 20))
|
|
if use_toggled:
|
|
button.toggled.connect(callback)
|
|
else:
|
|
button.clicked.connect(callback)
|
|
return button
|
|
|
|
# _build_preview_panel: function definition for ScanSlicer behavior.
|
|
def _build_preview_panel(self, root):
|
|
preview_panel = QWidget(root)
|
|
preview_layout = QGridLayout(preview_panel)
|
|
preview_layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
preview_toolbar = QWidget(preview_panel)
|
|
preview_toolbar_layout = QHBoxLayout(preview_toolbar)
|
|
preview_toolbar_layout.setContentsMargins(0, 0, 0, 0)
|
|
self.preview_toolbar = preview_toolbar
|
|
|
|
self.detect_button = self._create_toolbar_button(
|
|
preview_toolbar,
|
|
text="Detect",
|
|
tooltip="Detect pictures",
|
|
icon_data_url=self.DETECT_ICON_DATA_URL,
|
|
callback=lambda: self.update_preview(),
|
|
)
|
|
preview_toolbar_layout.addWidget(self.detect_button)
|
|
|
|
self.preview_filter_button = self._create_toolbar_button(
|
|
preview_toolbar,
|
|
text="Filter",
|
|
tooltip="Preview filter output",
|
|
icon_data_url=self.PREVIEW_FILTER_ICON_DATA_URL,
|
|
callback=self.set_preview_filter_enabled,
|
|
checkable=True,
|
|
use_toggled=True,
|
|
)
|
|
preview_toolbar_layout.addWidget(self.preview_filter_button)
|
|
|
|
self.reset_view_button = self._create_toolbar_button(
|
|
preview_toolbar,
|
|
text="Reset view",
|
|
tooltip="Reset and center view (95%)",
|
|
icon_data_url=self.RESET_VIEW_ICON_DATA_URL,
|
|
callback=self.reset_canvas_view,
|
|
)
|
|
preview_toolbar_layout.addWidget(self.reset_view_button)
|
|
|
|
self.active_box_label = QLabel("Active Box: none")
|
|
preview_toolbar_layout.addWidget(self.active_box_label)
|
|
preview_toolbar_layout.addStretch(1)
|
|
|
|
self.active_box_lock_button = self._create_toolbar_button(
|
|
preview_toolbar,
|
|
text="🔒",
|
|
tooltip="Lock/unlock active box",
|
|
icon_data_url=self.LOCK_ICON_DATA_URL,
|
|
callback=self.toggle_active_box_lock,
|
|
checkable=True,
|
|
enabled=False,
|
|
use_toggled=True,
|
|
)
|
|
preview_toolbar_layout.addWidget(self.active_box_lock_button)
|
|
|
|
self.all_boxes_lock_button = self._create_toolbar_button(
|
|
preview_toolbar,
|
|
text="Lock all",
|
|
tooltip="Lock or unlock all boxes",
|
|
icon_data_url=self.LOCK_ALL_ICON_DATA_URL,
|
|
callback=self.toggle_all_boxes_lock,
|
|
checkable=True,
|
|
enabled=False,
|
|
use_toggled=True,
|
|
)
|
|
preview_toolbar_layout.addWidget(self.all_boxes_lock_button)
|
|
|
|
self.active_box_squarize_button = self._create_toolbar_button(
|
|
preview_toolbar,
|
|
text="▭",
|
|
tooltip="Squarize active box",
|
|
icon_data_url=self.SQUARIZE_ICON_DATA_URL,
|
|
callback=self.squarize_active_box,
|
|
enabled=False,
|
|
)
|
|
preview_toolbar_layout.addWidget(self.active_box_squarize_button)
|
|
|
|
self.active_box_reset_rotation_button = self._create_toolbar_button(
|
|
preview_toolbar,
|
|
text="Reset rotation",
|
|
tooltip="Reset active box rotation/tilt",
|
|
icon_data_url=self.RESET_ROTATION_ICON_DATA_URL,
|
|
callback=self.reset_active_box_rotation,
|
|
enabled=False,
|
|
)
|
|
preview_toolbar_layout.addWidget(self.active_box_reset_rotation_button)
|
|
|
|
self.active_box_swap_button = self._create_toolbar_button(
|
|
preview_toolbar,
|
|
text="Swap L/R",
|
|
tooltip="Swap left/right orientation of the active box",
|
|
icon_data_url=self.SWAP_LR_ICON_DATA_URL,
|
|
callback=self.swap_active_box_left_right,
|
|
enabled=False,
|
|
)
|
|
preview_toolbar_layout.addWidget(self.active_box_swap_button)
|
|
|
|
self.add_box_button = self._create_toolbar_button(
|
|
preview_toolbar,
|
|
text="Add box",
|
|
tooltip="Add manual bounding box",
|
|
icon_data_url=self.ADD_BOX_ICON_DATA_URL,
|
|
callback=self.add_box,
|
|
)
|
|
preview_toolbar_layout.addWidget(self.add_box_button)
|
|
|
|
self.active_box_remove_button = self._create_toolbar_button(
|
|
preview_toolbar,
|
|
text="✕",
|
|
tooltip="Remove active box",
|
|
icon_data_url=self.REMOVE_ICON_DATA_URL,
|
|
callback=self.remove_active_box,
|
|
enabled=False,
|
|
)
|
|
preview_toolbar_layout.addWidget(self.active_box_remove_button)
|
|
|
|
self.active_box_rotate_button = self._create_toolbar_button(
|
|
preview_toolbar,
|
|
text="⟳",
|
|
tooltip="Rotate mode: drag active box corner handles to rotate",
|
|
icon_data_url=self.ROTATE_ICON_DATA_URL,
|
|
callback=self.set_rotate_handles_mode,
|
|
checkable=True,
|
|
use_toggled=True,
|
|
)
|
|
preview_toolbar_layout.addWidget(self.active_box_rotate_button)
|
|
|
|
self.slicing_canvas = QtSlicingCanvas(preview_panel)
|
|
self.slicing_canvas.active_slice_changed.connect(self.on_active_slice_changed)
|
|
|
|
preview_layout.addWidget(preview_toolbar, 0, 0)
|
|
preview_layout.addWidget(self.slicing_canvas, 1, 0)
|
|
preview_layout.setRowStretch(1, 1)
|
|
return preview_panel
|
|
|
|
# _build_status_bar: function definition for ScanSlicer behavior.
|
|
def _build_status_bar(self):
|
|
status_bar = self.statusBar()
|
|
status_bar.setMinimumHeight(30)
|
|
status_bar.setSizeGripEnabled(False)
|
|
status_bar.setStyleSheet("QStatusBar::item { border: none; }")
|
|
self.status_message_label = QLabel("Ready.")
|
|
self.status_message_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
|
|
status_message_container = QWidget(status_bar)
|
|
status_message_layout = QHBoxLayout(status_message_container)
|
|
status_message_layout.setContentsMargins(10, 0, 10, 6)
|
|
status_message_layout.setSpacing(0)
|
|
status_message_layout.addWidget(self.status_message_label)
|
|
status_bar.addWidget(status_message_container, 1)
|
|
|
|
self.status_filename_label = QLabel("No file")
|
|
self.status_filename_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
|
|
status_filename_container = QWidget(status_bar)
|
|
status_filename_layout = QHBoxLayout(status_filename_container)
|
|
status_filename_layout.setContentsMargins(10, 0, 10, 6)
|
|
status_filename_layout.setSpacing(0)
|
|
status_filename_layout.addWidget(self.status_filename_label)
|
|
status_bar.addPermanentWidget(status_filename_container)
|
|
|
|
|
|
__all__ = ["ScanSlicerUiPreviewMixin"]
|