diff --git a/BlocksScreen/lib/panels/mainWindow.py b/BlocksScreen/lib/panels/mainWindow.py index 32355803..66f792de 100644 --- a/BlocksScreen/lib/panels/mainWindow.py +++ b/BlocksScreen/lib/panels/mainWindow.py @@ -13,6 +13,7 @@ from lib.panels.printTab import PrintTab from lib.panels.utilitiesTab import UtilitiesTab from lib.panels.widgets.connectionPage import ConnectionPage +from lib.panels.widgets.cancelPage import CancelPage from lib.panels.widgets.popupDialogWidget import Popup from lib.printer import Printer from lib.ui.mainWindow_ui import Ui_MainWindow # With header @@ -65,6 +66,9 @@ class MainWindow(QtWidgets.QMainWindow): on_update_message: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( dict, name="on-update-message" ) + run_gcode_signal: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( + str, name="run_gcode" + ) call_load_panel = QtCore.pyqtSignal(bool, str, name="call-load-panel") def __init__(self): @@ -84,6 +88,8 @@ def __init__(self): self.conn_window = ConnectionPage(self, self.ws) self.up = UpdatePage(self) self.up.hide() + + self.conn_window.call_cancel_panel.connect(self.handle_cancel_print) self.installEventFilter(self.conn_window) self.printPanel = PrintTab( self.ui.printTab, self.file_data, self.ws, self.printer @@ -152,6 +158,8 @@ def __init__(self): self.query_object_list.connect(self.utilitiesPanel.on_object_list) self.printer.extruder_update.connect(self.on_extruder_update) self.printer.heater_bed_update.connect(self.on_heater_bed_update) + self.run_gcode_signal.connect(self.ws.api.run_gcode) + self.ui.main_content_widget.currentChanged.connect(slot=self.reset_tab_indexes) self.call_network_panel.connect(self.networkPanel.show_network_panel) self.conn_window.wifi_button_clicked.connect(self.call_network_panel.emit) @@ -191,11 +199,40 @@ def __init__(self): self, LoadingOverlayWidget.AnimationGIF.DEFAULT ) self.loadscreen.add_widget(self.loadwidget) + + self.cancelpage = CancelPage(self, ws=self.ws) + self.cancelpage.request_file_info.connect(self.file_data.on_request_fileinfo) + self.cancelpage.run_gcode.connect(self.ws.api.run_gcode) + self.printer.print_stats_update[str, str].connect( + self.cancelpage.on_print_stats_update + ) + self.printer.print_stats_update[str, dict].connect( + self.cancelpage.on_print_stats_update + ) + self.printer.print_stats_update[str, float].connect( + self.cancelpage.on_print_stats_update + ) + self.file_data.fileinfo.connect(self.cancelpage._show_screen_thumbnail) + self.printPanel.call_cancel_panel.connect(self.handle_cancel_print) + if self.config.has_section("server"): # @ Start websocket connection with moonraker self.bo_ws_startup.emit() self.reset_tab_indexes() + @QtCore.pyqtSlot(bool, name="show-cancel-page") + def handle_cancel_print(self, show: bool = True): + """Slot for displaying update Panel""" + if not show: + self.cancelpage.hide() + return + + self.cancelpage.setGeometry(0, 0, self.width(), self.height()) + self.cancelpage.raise_() + self.cancelpage.updateGeometry() + self.cancelpage.repaint() + self.cancelpage.show() + @QtCore.pyqtSlot(bool, str, name="show-load-page") def show_LoadScreen(self, show: bool = True, msg: str = ""): _sender = self.sender() @@ -734,6 +771,8 @@ def event(self, event: QtCore.QEvent) -> bool: events.PrintComplete.type(), events.PrintCancelled.type(), ): + if event.type() == events.PrintCancelled.type(): + self.handle_cancel_print() self.enable_tab_bar() self.ui.extruder_temp_display.clicked.disconnect() self.ui.bed_temp_display.clicked.disconnect() diff --git a/BlocksScreen/lib/panels/printTab.py b/BlocksScreen/lib/panels/printTab.py index 7431938e..b28e52bd 100644 --- a/BlocksScreen/lib/panels/printTab.py +++ b/BlocksScreen/lib/panels/printTab.py @@ -64,6 +64,7 @@ class PrintTab(QtWidgets.QStackedWidget): ) call_load_panel = QtCore.pyqtSignal(bool, str, name="call-load-panel") + call_cancel_panel = QtCore.pyqtSignal(bool, name="call-load-panel") _z_offset: float = 0.0 _active_z_offset: float = 0.0 _finish_print_handled: bool = False @@ -124,6 +125,7 @@ def __init__( self.file_data.request_file_list ) self.file_data.on_dirs.connect(self.filesPage_widget.on_directories) + self.filesPage_widget.request_dir_info[str].connect( self.file_data.request_dir_info[str] ) @@ -137,6 +139,7 @@ def __init__( self.jobStatusPage_widget.show_request.connect( lambda: self.change_page(self.indexOf(self.jobStatusPage_widget)) ) + self.jobStatusPage_widget.call_cancel_panel.connect(self.call_cancel_panel) self.jobStatusPage_widget.hide_request.connect( lambda: self.change_page(self.indexOf(self.print_page)) ) diff --git a/BlocksScreen/lib/panels/widgets/cancelPage.py b/BlocksScreen/lib/panels/widgets/cancelPage.py new file mode 100644 index 00000000..e16fb2e6 --- /dev/null +++ b/BlocksScreen/lib/panels/widgets/cancelPage.py @@ -0,0 +1,267 @@ +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 +import typing + +from lib.moonrakerComm import MoonWebSocket + + +class CancelPage(QtWidgets.QWidget): + """Update GUI Page, + retrieves from moonraker available clients and adds functionality + for updating or recovering them + """ + + 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, ws: MoonWebSocket) -> None: + super().__init__(parent) + self.ws: MoonWebSocket = ws + self._setupUI() + self.filename = "" + + self.reprint_start.connect(self.ws.api.start_print) + + 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") + + @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) + + 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, dict): + try: + thumbnails = dict["thumbnail_images"] + + last_thumb = QtGui.QPixmap.fromImage(thumbnails[-1]) + + if last_thumb.isNull(): + last_thumb = QtGui.QPixmap( + "BlocksScreen/lib/ui/resources/media/logoblocks400x300.png" + ) + except Exception as e: + print(e) + last_thumb = QtGui.QPixmap( + "BlocksScreen/lib/ui/resources/media/logoblocks400x300.png" + ) + self.set_pixmap(last_thumb) + + 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("Print job was\ncancelled") + 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/connectionPage.py b/BlocksScreen/lib/panels/widgets/connectionPage.py index 9403d290..a03e42dd 100644 --- a/BlocksScreen/lib/panels/widgets/connectionPage.py +++ b/BlocksScreen/lib/panels/widgets/connectionPage.py @@ -15,6 +15,7 @@ class ConnectionPage(QtWidgets.QFrame): firmware_restart_clicked = QtCore.pyqtSignal(name="firmware_restart_clicked") update_button_clicked = QtCore.pyqtSignal(bool, name="show-update-page") call_load_panel = QtCore.pyqtSignal(bool, str, name="call-load-panel") + call_cancel_panel = QtCore.pyqtSignal(bool, name="call-load-panel") def __init__(self, parent: QtWidgets.QWidget, ws: MoonWebSocket, /): super().__init__(parent) @@ -67,6 +68,7 @@ def showEvent(self, a0: QtCore.QEvent | None): """Handle show event""" self.ws.api.refresh_update_status() self.call_load_panel.emit(False, "") + self.call_cancel_panel.emit(False) return super().showEvent(a0) @QtCore.pyqtSlot(bool, name="on_klippy_connected") diff --git a/BlocksScreen/lib/panels/widgets/jobStatusPage.py b/BlocksScreen/lib/panels/widgets/jobStatusPage.py index bd5bbb8c..f868c222 100644 --- a/BlocksScreen/lib/panels/widgets/jobStatusPage.py +++ b/BlocksScreen/lib/panels/widgets/jobStatusPage.py @@ -53,6 +53,7 @@ class JobStatusWidget(QtWidgets.QWidget): request_file_info: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( str, name="request_file_info" ) + call_cancel_panel = QtCore.pyqtSignal(bool, name="call-load-panel") _internal_print_status: str = "" _current_file_name: str = "" @@ -225,18 +226,19 @@ def _handle_print_state(self, state: str) -> None: ) self.pause_printing_btn.setEnabled(True) self.request_query_print_stats.emit({"print_stats": ["filename"]}) + self.call_cancel_panel.emit(False) self.show_request.emit() lstate = "start" elif lstate in invalid_states: if lstate != "standby": self.print_finish.emit() - self._current_file_name = "" self._internal_print_status = "" + self._current_file_name = "" self.total_layers = "?" self.file_metadata.clear() self.hide_request.emit() - if hasattr(self, "thumbnail_view"): - getattr(self, "thumbnail_view").deleteLater() + # if hasattr(self, "thumbnail_view"): + # getattr(self, "thumbnail_view").deleteLater() # Send Event on Print state if hasattr(events, str("Print" + lstate.capitalize())): event_obj = getattr(events, str("Print" + lstate.capitalize()))