diff --git a/BlocksScreen/lib/panels/filamentTab.py b/BlocksScreen/lib/panels/filamentTab.py index f8d472e2..3dd67a11 100644 --- a/BlocksScreen/lib/panels/filamentTab.py +++ b/BlocksScreen/lib/panels/filamentTab.py @@ -4,16 +4,16 @@ from devices.amu import AMUManager from devices.amu.models import GateStatus -from lib.panels.widgets.addFilamentPage import AddFilamentPage -from lib.panels.widgets.addSpoolPage import AddSpoolPage -from lib.panels.widgets.amuPage import AMUpage from lib.panels.widgets.basePopup import BasePopup -from lib.panels.widgets.basicFilamentPanel import BasicFilamentPanel from lib.panels.widgets.colorWheelWidget import ColorWheelWidget +from lib.panels.widgets.FilamentTab.addFilamentPage import AddFilamentPage +from lib.panels.widgets.FilamentTab.addSpoolPage import AddSpoolPage +from lib.panels.widgets.FilamentTab.amuPage import AMUpage +from lib.panels.widgets.FilamentTab.basicFilamentPanel import BasicFilamentPanel +from lib.panels.widgets.FilamentTab.spoolmanPage import SpoolmanPage from lib.panels.widgets.keyboardPage import CustomQwertyKeyboard from lib.panels.widgets.loadWidget import LoadingOverlayWidget from lib.panels.widgets.numpadPage import CustomNumpad -from lib.panels.widgets.spoolmanPage import SpoolmanPage from lib.printer import Printer from lib.utils.blocks_button import BlocksCustomButton from lib.utils.blocks_frame import BlocksCustomFrame @@ -48,8 +48,8 @@ def __init__( self.amu_manager: AMUManager = amu_manager self.amu_configured = False self._popup_callback = None - self.ui = self.setupUi() - self.change_page(self.indexOf(self.ui)) + self._setup_ui() + self.change_page(self.indexOf(self.filament_control_page)) self._previous_gate_states: dict[int, bool] = {} self.pre_gate_idx = {} @@ -1033,51 +1033,37 @@ def setupUi(self): widget.setMinimumSize(QtCore.QSize(710, 410)) widget.setMaximumSize(QtCore.QSize(710, 410)) self.setObjectName("filament_page") - self.verticalLayout = QtWidgets.QVBoxLayout() + self.verticalLayout = QtWidgets.QVBoxLayout(widget) self.verticalLayout.setObjectName("verticalLayout") self.fp_header_layout = QtWidgets.QHBoxLayout() self.fp_header_layout.setObjectName("fp_header_layout") - self.fp_header_layout.addItem( - QtWidgets.QSpacerItem( - 60, - 60, - QtWidgets.QSizePolicy.Policy.Fixed, - QtWidgets.QSizePolicy.Policy.Minimum, - ) - ) - self.fp_header_title = QtWidgets.QLabel(parent=self) + self.fp_hblank_left = QtWidgets.QWidget() + self.fp_hblank_left.setMinimumSize(QtCore.QSize(60, 55)) + self.fp_hblank_left.setMaximumSize(QtCore.QSize(60, 55)) + self.fp_header_layout.addWidget(self.fp_hblank_left) + + self.fp_header_title = QtWidgets.QLabel(parent=widget) sizePolicy = QtWidgets.QSizePolicy( - QtWidgets.QSizePolicy.Policy.MinimumExpanding, - QtWidgets.QSizePolicy.Policy.Fixed, - ) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth( - self.fp_header_title.sizePolicy().hasHeightForWidth() + QtWidgets.QSizePolicy.Policy.Expanding, + QtWidgets.QSizePolicy.Policy.Minimum, ) self.fp_header_title.setSizePolicy(sizePolicy) - self.fp_header_title.setMinimumSize(QtCore.QSize(300, 60)) + self.fp_header_title.setMinimumSize(QtCore.QSize(0, 60)) self.fp_header_title.setMaximumSize(QtCore.QSize(16777215, 60)) font = QtGui.QFont() font.setFamily("Momcake") font.setPointSize(24) - font.setBold(True) - font.setWeight(75) self.fp_header_title.setFont(font) self.fp_header_title.setStyleSheet("background: transparent; color: white;") self.fp_header_title.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) self.fp_header_title.setObjectName("fp_header_title") self.fp_header_layout.addWidget(self.fp_header_title) - self.fp_header_layout.addItem( - QtWidgets.QSpacerItem( - 60, - 60, - QtWidgets.QSizePolicy.Policy.Fixed, - QtWidgets.QSizePolicy.Policy.Minimum, - ) - ) + self.fp_hblank_right = QtWidgets.QWidget() + self.fp_hblank_right.setMinimumSize(QtCore.QSize(60, 55)) + self.fp_hblank_right.setMaximumSize(QtCore.QSize(60, 55)) + self.fp_header_layout.addWidget(self.fp_hblank_right) self.verticalLayout.addLayout(self.fp_header_layout) self.fp_content_layout = QtWidgets.QGridLayout() @@ -1124,16 +1110,20 @@ def setupUi(self): self.fp_content_layout.addWidget(self.fp_button_2, 1, 1, 1, 1) - self.verticalLayout.addLayout(self.fp_content_layout) - widget.setLayout(self.verticalLayout) + self.fp_content_layout.setRowMinimumHeight(0, 80) + self.fp_content_layout.setRowMinimumHeight(1, 80) + self.fp_content_layout.setRowMinimumHeight(2, 80) + self.fp_content_layout.setContentsMargins(0, 0, 0, 0) - self.fp_content_layout.setRowMinimumHeight( - 0, int(87.5) - ) # 87.5 to compensate for not having margin on the rest of the buttons - self.fp_content_layout.setRowMinimumHeight( - 2, int(87.5) - ) # dont ask how i got this value , it was try and repeat + fp_content_widget = QtWidgets.QWidget(parent=widget) + fp_content_widget.setLayout(self.fp_content_layout) + fp_content_widget.setSizePolicy( + QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Fixed + ) + fp_content_widget.setFixedHeight(80 * 3 + self.fp_content_layout.spacing() * 2) + self.verticalLayout.addWidget(fp_content_widget) + self.filament_control_page = widget self.addWidget(widget) self.fp_header_title.setText("Filament") self.fp_button_1.setText("Filament\nControl") diff --git a/BlocksScreen/lib/panels/widgets/addFilamentPage.py b/BlocksScreen/lib/panels/widgets/FilamentTab/addFilamentPage.py similarity index 100% rename from BlocksScreen/lib/panels/widgets/addFilamentPage.py rename to BlocksScreen/lib/panels/widgets/FilamentTab/addFilamentPage.py diff --git a/BlocksScreen/lib/panels/widgets/addSpoolPage.py b/BlocksScreen/lib/panels/widgets/FilamentTab/addSpoolPage.py similarity index 100% rename from BlocksScreen/lib/panels/widgets/addSpoolPage.py rename to BlocksScreen/lib/panels/widgets/FilamentTab/addSpoolPage.py diff --git a/BlocksScreen/lib/panels/widgets/amuPage.py b/BlocksScreen/lib/panels/widgets/FilamentTab/amuPage.py similarity index 99% rename from BlocksScreen/lib/panels/widgets/amuPage.py rename to BlocksScreen/lib/panels/widgets/FilamentTab/amuPage.py index 3648aaf9..140f0afb 100644 --- a/BlocksScreen/lib/panels/widgets/amuPage.py +++ b/BlocksScreen/lib/panels/widgets/FilamentTab/amuPage.py @@ -1,7 +1,7 @@ import typing from devices.amu import AMUManager -from lib.panels.widgets.amuWidgets import SpoolCarousel, SpoolInfoPanel +from lib.panels.widgets.FilamentTab.amuWidgets import SpoolCarousel, SpoolInfoPanel from lib.panels.widgets.basePopup import BasePopup from lib.utils.blocks_frame import BlocksCustomFrame from lib.utils.icon_button import IconButton diff --git a/BlocksScreen/lib/panels/widgets/amuWidgets.py b/BlocksScreen/lib/panels/widgets/FilamentTab/amuWidgets.py similarity index 100% rename from BlocksScreen/lib/panels/widgets/amuWidgets.py rename to BlocksScreen/lib/panels/widgets/FilamentTab/amuWidgets.py diff --git a/BlocksScreen/lib/panels/widgets/basicFilamentPanel.py b/BlocksScreen/lib/panels/widgets/FilamentTab/basicFilamentPanel.py similarity index 99% rename from BlocksScreen/lib/panels/widgets/basicFilamentPanel.py rename to BlocksScreen/lib/panels/widgets/FilamentTab/basicFilamentPanel.py index 9dc0c8d2..0340b073 100644 --- a/BlocksScreen/lib/panels/widgets/basicFilamentPanel.py +++ b/BlocksScreen/lib/panels/widgets/FilamentTab/basicFilamentPanel.py @@ -57,7 +57,7 @@ def __init__( self.mmu_configured = False self.load_popup = load_popup self._mmu_state = None - self._setupUi() + self._setup_ui() self.filament_state = self.FilamentStates.UNKNOWN self.setCurrentIndex(0) @@ -368,7 +368,8 @@ def _setupInfoBox(self): return root - def _setupUi(self): + def _setup_ui(self) -> None: + """Build the basic filament panel: load/unload controls and status labels.""" self.setObjectName("self") self.resize(710, 411) diff --git a/BlocksScreen/lib/panels/widgets/spoolmanPage.py b/BlocksScreen/lib/panels/widgets/FilamentTab/spoolmanPage.py similarity index 99% rename from BlocksScreen/lib/panels/widgets/spoolmanPage.py rename to BlocksScreen/lib/panels/widgets/FilamentTab/spoolmanPage.py index 3166c27f..a50d1e80 100644 --- a/BlocksScreen/lib/panels/widgets/spoolmanPage.py +++ b/BlocksScreen/lib/panels/widgets/FilamentTab/spoolmanPage.py @@ -1,8 +1,8 @@ import logging import typing -from lib.panels.widgets.addFilamentPage import AddFilamentPage -from lib.panels.widgets.addSpoolPage import AddSpoolPage +from lib.panels.widgets.FilamentTab.addFilamentPage import AddFilamentPage +from lib.panels.widgets.FilamentTab.addSpoolPage import AddSpoolPage from lib.panels.widgets.basePopup import BasePopup from lib.utils.blocks_frame import BlocksCustomFrame from lib.utils.icon_button import IconButton diff --git a/BlocksScreen/lib/panels/widgets/MainWindow/cancelPage.py b/BlocksScreen/lib/panels/widgets/MainWindow/cancelPage.py index 8592e578..d05ec9c5 100644 --- a/BlocksScreen/lib/panels/widgets/MainWindow/cancelPage.py +++ b/BlocksScreen/lib/panels/widgets/MainWindow/cancelPage.py @@ -273,3 +273,280 @@ def _setupUI(self) -> None: self.cf_thumbnail, 0, QtCore.Qt.AlignmentFlag.AlignCenter ) self.verticalLayout_4.addLayout(self.cf_content_vertical_layout) + + +import logging +import typing + +from lib.utils.blocks_button import BlocksCustomButton +from lib.utils.blocks_frame import BlocksCustomFrame +from lib.utils.blocks_label import BlocksLabel +from PyQt6 import QtCore, QtGui, QtWidgets + +logger = logging.getLogger(__name__) + + +class CancelPage(QtWidgets.QWidget): + """Displayed when a print is cancelled; offers reprint or ignore.""" + + request_file_info: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( + str, name="request_file_info" + ) + reprint_start: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( + str, name="reprint_start" + ) + run_gcode: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( + str, name="run_gcode" + ) + + def __init__(self, parent: QtWidgets.QWidget) -> None: + super().__init__(parent) + self._setupUI() + self.filename = "" + + self.confirm_button.clicked.connect(lambda: self._handle_accept()) + self.refuse_button.clicked.connect(lambda: self._handle_refuse()) + + self.setAttribute(QtCore.Qt.WidgetAttribute.WA_StyledBackground, True) + + def _handle_accept(self): + self.run_gcode.emit("SDCARD_RESET_FILE") + self.reprint_start.emit(self.filename) + self.close() + + def _handle_refuse(self): + self.close() + self.run_gcode.emit("SDCARD_RESET_FILE") + + _REASON_HEADERS: typing.ClassVar[dict[str, str]] = { + "complete": "Print Finished", + "error": "Print Error", + "cancelled": "Print Cancelled", + } + + @QtCore.pyqtSlot(str, dict, name="on_print_stats_update") + @QtCore.pyqtSlot(str, float, name="on_print_stats_update") + @QtCore.pyqtSlot(str, str, name="on_print_stats_update") + def on_print_stats_update(self, field: str, value: dict | float | str) -> None: + if isinstance(value, str): + if "filename" in field: + self.filename = value + if self.isVisible(): + self.set_file_name(value) + elif "state" in field: + if value in self._REASON_HEADERS: + self.cf_info_tf.setText(self._REASON_HEADERS[value]) + elif value == "printing": + # New job: drop the previous job's reason so it cannot leak. + self.cf_info_tf.clear() + + def show(self): + self.request_file_info.emit(self.filename) + + super().show() + + def set_pixmap(self, pixmap: QtGui.QPixmap) -> None: + if not hasattr(self, "_scene"): + self._scene = QtWidgets.QGraphicsScene(self) + self.cf_thumbnail.setScene(self._scene) + + # Scene rectangle (available display area) + graphics_rect = self.cf_thumbnail.rect().toRectF() + + # Scale pixmap preserving aspect ratio + pixmap = pixmap.scaled( + graphics_rect.size().toSize(), + QtCore.Qt.AspectRatioMode.KeepAspectRatio, + QtCore.Qt.TransformationMode.SmoothTransformation, + ) + + adjusted_x = (graphics_rect.width() - pixmap.width()) / 2.0 + adjusted_y = (graphics_rect.height() - pixmap.height()) / 2.0 + + if not hasattr(self, "_pixmap_item"): + self._pixmap_item = QtWidgets.QGraphicsPixmapItem(pixmap) + self._scene.addItem(self._pixmap_item) + else: + self._pixmap_item.setPixmap(pixmap) + + self._pixmap_item.setPos(adjusted_x, adjusted_y) + self._scene.setSceneRect(graphics_rect) + + def set_file_name(self, file_name: str) -> None: + self.cf_file_name.setText(file_name) + + def _show_screen_thumbnail(self, metadata: dict | None) -> None: + """Display the largest thumbnail from file metadata. + + ``thumbnail_images`` values are pre-loaded ``QImage`` + objects produced by ``Files._process_metadata``. + """ + fallback = QtGui.QPixmap( + "BlocksScreen/lib/ui/resources/media/logoblocks400x300.png" + ) + thumbnails = metadata.get("thumbnail_images", []) if metadata else [] + if not thumbnails: + self.set_pixmap(fallback) + return + + last_thumb = thumbnails[-1] + if isinstance(last_thumb, QtGui.QImage) and not last_thumb.isNull(): + self.set_pixmap(QtGui.QPixmap.fromImage(last_thumb)) + else: + self.set_pixmap(fallback) + + def _setupUI(self) -> None: + """Setup widget ui""" + sizePolicy = QtWidgets.QSizePolicy( + QtWidgets.QSizePolicy.Policy.MinimumExpanding, + QtWidgets.QSizePolicy.Policy.MinimumExpanding, + ) + sizePolicy.setHorizontalStretch(1) + sizePolicy.setVerticalStretch(1) + sizePolicy.setHeightForWidth(self.sizePolicy().hasHeightForWidth()) + self.setSizePolicy(sizePolicy) + self.setObjectName("cancelPage") + self.setStyleSheet("""#cancelPage { + background-image: url(:/background/media/1st_background.png); + }""") + self.setMinimumSize(QtCore.QSize(800, 480)) + self.setMaximumSize(QtCore.QSize(800, 480)) + self.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) + self.verticalLayout_4 = QtWidgets.QVBoxLayout(self) + self.verticalLayout_4.setObjectName("verticalLayout_4") + self.cf_header_title = QtWidgets.QHBoxLayout() + self.cf_header_title.setObjectName("cf_header_title") + + sizePolicy = QtWidgets.QSizePolicy( + QtWidgets.QSizePolicy.Policy.Expanding, + QtWidgets.QSizePolicy.Policy.Expanding, + ) + self.cf_file_name = BlocksLabel(parent=self) + self.cf_file_name.setMinimumSize(QtCore.QSize(0, 60)) + self.cf_file_name.setMaximumSize(QtCore.QSize(16777215, 60)) + font = QtGui.QFont() + font.setFamily("Momcake") + font.setPointSize(24) + self.cf_file_name.setFont(font) + self.cf_file_name.setLayoutDirection(QtCore.Qt.LayoutDirection.RightToLeft) + self.cf_file_name.setSizePolicy(sizePolicy) + self.cf_file_name.setStyleSheet("background: transparent; color: white;") + self.cf_file_name.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) + self.cf_file_name.setObjectName("cf_file_name") + self.cf_header_title.addWidget(self.cf_file_name) + + self.verticalLayout_4.addLayout(self.cf_header_title) + self.cf_content_vertical_layout = QtWidgets.QHBoxLayout() + self.cf_content_vertical_layout.setObjectName("cf_content_vertical_layout") + self.cf_content_horizontal_layout = QtWidgets.QVBoxLayout() + self.cf_content_horizontal_layout.setObjectName("cf_content_horizontal_layout") + self.info_frame = BlocksCustomFrame(parent=self) + sizePolicy = QtWidgets.QSizePolicy( + QtWidgets.QSizePolicy.Policy.Expanding, + QtWidgets.QSizePolicy.Policy.Expanding, + ) + self.info_frame.setSizePolicy(sizePolicy) + + self.info_frame.setFrameShadow(QtWidgets.QFrame.Shadow.Raised) + + self.info_layout = QtWidgets.QVBoxLayout(self.info_frame) + + self.cf_info_tf = QtWidgets.QLabel(parent=self.info_frame) + self.cf_info_tf.setText(self._REASON_HEADERS["cancelled"]) + font = QtGui.QFont() + font.setFamily("Momcake") + font.setPointSize(20) + + self.cf_info_tf.setFont(font) + self.cf_info_tf.setStyleSheet("background: transparent; color: white;") + + self.cf_info_tf.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) + self.info_layout.addWidget(self.cf_info_tf) + + self.cf_info_tr = QtWidgets.QLabel(parent=self.info_frame) + font = QtGui.QFont() + font.setPointSize(15) + self.cf_info_tr.setFont(font) + self.cf_info_tr.setStyleSheet("background: transparent; color: white;") + self.cf_info_tr.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) + self.cf_info_tr.setText("Do you want to reprint?") + self.info_layout.addWidget(self.cf_info_tr) + + self.cf_confirm_layout = QtWidgets.QVBoxLayout() + self.cf_confirm_layout.setSpacing(15) + + self.confirm_button = BlocksCustomButton(parent=self.info_frame) + self.confirm_button.setMinimumSize(QtCore.QSize(250, 70)) + self.confirm_button.setMaximumSize(QtCore.QSize(250, 70)) + font = QtGui.QFont("Momcake", 18) + self.confirm_button.setFont(font) + self.confirm_button.setFlat(True) + self.confirm_button.setProperty( + "icon_pixmap", QtGui.QPixmap(":/dialog/media/btn_icons/yes.svg") + ) + self.confirm_button.setText("Reprint") + # 2. Align buttons to the right + self.cf_confirm_layout.addWidget( + self.confirm_button, 0, QtCore.Qt.AlignmentFlag.AlignCenter + ) + + self.refuse_button = BlocksCustomButton(parent=self.info_frame) + self.refuse_button.setMinimumSize(QtCore.QSize(250, 70)) + self.refuse_button.setMaximumSize(QtCore.QSize(250, 70)) + self.refuse_button.setFont(font) + self.refuse_button.setFlat(True) + self.refuse_button.setProperty( + "icon_pixmap", QtGui.QPixmap(":/dialog/media/btn_icons/no.svg") + ) + self.refuse_button.setText("Ignore") + # 2. Align buttons to the right + self.cf_confirm_layout.addWidget( + self.refuse_button, 0, QtCore.Qt.AlignmentFlag.AlignCenter + ) + + self.info_layout.addLayout(self.cf_confirm_layout) + + self.cf_content_horizontal_layout.addWidget(self.info_frame) + + self.cf_content_vertical_layout.addLayout(self.cf_content_horizontal_layout) + self.cf_thumbnail = QtWidgets.QGraphicsView(self) + sizePolicy = QtWidgets.QSizePolicy( + QtWidgets.QSizePolicy.Policy.Expanding, + QtWidgets.QSizePolicy.Policy.Expanding, + ) + sizePolicy.setHorizontalStretch(1) + sizePolicy.setVerticalStretch(1) + sizePolicy.setHeightForWidth(self.cf_thumbnail.sizePolicy().hasHeightForWidth()) + self.cf_thumbnail.setSizePolicy(sizePolicy) + self.cf_thumbnail.setMinimumSize(QtCore.QSize(400, 300)) + self.cf_thumbnail.setMaximumSize(QtCore.QSize(400, 300)) + self.cf_thumbnail.setStyleSheet( + "QGraphicsView{\nbackground-color: transparent;\n}" + ) + self.cf_thumbnail.setFrameShape(QtWidgets.QFrame.Shape.NoFrame) + self.cf_thumbnail.setFrameShadow(QtWidgets.QFrame.Shadow.Plain) + self.cf_thumbnail.setVerticalScrollBarPolicy( + QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff + ) + self.cf_thumbnail.setHorizontalScrollBarPolicy( + QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff + ) + self.cf_thumbnail.setSizeAdjustPolicy( + QtWidgets.QAbstractScrollArea.SizeAdjustPolicy.AdjustIgnored + ) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0, 0)) + brush.setStyle(QtCore.Qt.BrushStyle.NoBrush) + self.cf_thumbnail.setBackgroundBrush(brush) + self.cf_thumbnail.setRenderHints( + QtGui.QPainter.RenderHint.Antialiasing + | QtGui.QPainter.RenderHint.SmoothPixmapTransform + | QtGui.QPainter.RenderHint.TextAntialiasing + ) + self.cf_thumbnail.setViewportUpdateMode( + QtWidgets.QGraphicsView.ViewportUpdateMode.SmartViewportUpdate + ) + self.cf_thumbnail.setObjectName("cf_thumbnail") + self.cf_content_vertical_layout.addWidget( + self.cf_thumbnail, 0, QtCore.Qt.AlignmentFlag.AlignCenter + ) + self.verticalLayout_4.addLayout(self.cf_content_vertical_layout) diff --git a/BlocksScreen/lib/panels/widgets/fansPage.py b/BlocksScreen/lib/panels/widgets/fansPage.py deleted file mode 100644 index c31a8600..00000000 --- a/BlocksScreen/lib/panels/widgets/fansPage.py +++ /dev/null @@ -1,14 +0,0 @@ -from PyQt6 import QtCore, QtWidgets - - -class FansPage(QtWidgets.QWidget): - def __init__( - self, - parent: QtWidgets.QWidget | None, - flags: QtCore.Qt.WindowType | None, - ) -> None: - if parent is not None and flags is not None: - super().__init__(parent, flags) - - else: - super().__init__() diff --git a/tests/widgets/test_basic_filament_panel.py b/tests/widgets/test_basic_filament_panel.py index 6a7f616f..420b36b4 100644 --- a/tests/widgets/test_basic_filament_panel.py +++ b/tests/widgets/test_basic_filament_panel.py @@ -40,7 +40,7 @@ def __init__(self, name="", temperature=0): import pytest # noqa: E402 -from BlocksScreen.lib.panels.widgets.basicFilamentPanel import ( # noqa: E402 +from BlocksScreen.lib.panels.widgets.FilamentTab.basicFilamentPanel import ( # noqa: E402 BasicFilamentPanel, )