From f30ad6a2c164fa5c0dba10c3126e98b2f8ea82a6 Mon Sep 17 00:00:00 2001 From: Guilherme Costa Date: Wed, 9 Sep 2026 11:59:23 +0100 Subject: [PATCH 1/7] refactor(notification): clean up NotificationPage, fix dedupe, show newest first, add icon-text spacing --- .../lib/panels/widgets/notificationPage.py | 155 +++++++++--------- BlocksScreen/lib/utils/list_model.py | 61 +++---- tests/util/test_notification_page_unit.py | 4 +- 3 files changed, 107 insertions(+), 113 deletions(-) diff --git a/BlocksScreen/lib/panels/widgets/notificationPage.py b/BlocksScreen/lib/panels/widgets/notificationPage.py index 99a4d75e..bfb29ae3 100644 --- a/BlocksScreen/lib/panels/widgets/notificationPage.py +++ b/BlocksScreen/lib/panels/widgets/notificationPage.py @@ -1,4 +1,3 @@ -from collections import deque from typing import ClassVar from lib.panels.widgets.popupDialogWidget import Popup @@ -10,14 +9,8 @@ class NotificationPage(QtWidgets.QWidget): - """Update GUI Page, - retrieves from moonraker available clients and adds functionality - for updating or recovering them - """ + """Notification panel, lists moonraker/UI notifications and lets the user clear them""" - on_update_message: ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( - dict, name="on-update-message" - ) has_new_notification: ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( bool, name="has-new-notification" ) @@ -28,18 +21,18 @@ def __init__(self, parent=None) -> None: self._ICON_WARN = QtGui.QPixmap(":/ui/media/btn_icons/troubleshoot.svg") self._ICON_ERROR = QtGui.QPixmap(":/ui/media/btn_icons/error.svg") self._setupUI() - self.cli_tracking: deque = deque() self.selected_item: ListItem | None = None self.popup = Popup(self) self.model = EntryListModel() - self.model.setParent(self.update_buttons_list_widget) + self.model.setParent(self.notification_list_view) self.entry_delegate = EntryDelegate() - self.update_buttons_list_widget.setModel(self.model) - self.update_buttons_list_widget.setItemDelegate(self.entry_delegate) + self.notification_list_view.setModel(self.model) + self.notification_list_view.setItemDelegate(self.entry_delegate) self.entry_delegate.item_selected.connect(self.on_item_clicked) + self.model.rowsInserted.connect(self._on_rows_inserted) - self.update_back_btn.clicked.connect(self.hide) + self.back_btn.clicked.connect(self.hide) self.delete_btn.clicked.connect(self.delete_selected_item) self.delete_all_btn.clicked.connect(self.reset_view_model) @@ -77,23 +70,34 @@ def reset_view_model(self) -> None: self.entry_delegate.clear() self.has_new_notification.emit(False) - def build_model_list(self) -> None: - """Builds the model list (`self.model`) containing updatable clients""" - self.update_buttons_list_widget.blockSignals(True) + def _on_rows_inserted( + self, _parent: QtCore.QModelIndex, first: int, _last: int + ) -> None: + """Keep the delegate's prev_index valid when a notification is prepended above it.""" + if first <= self.entry_delegate.prev_index: + self.entry_delegate.prev_index += 1 + + def _ingest_notification(self, message: str, priority: int) -> None: + """Adds *message* to the model, collapsing a repeat of the last entry (moonraker echo spam).""" + match priority: + case 1: + color, icon = "#1A8FBF", self._ICON_INFO + case 2: + color, icon = "#E7E147", self._ICON_WARN + case 3: + color, icon = "#CA4949", self._ICON_ERROR + case _: + color, icon = "#a4a4a4", self._ICON_INFO + + if self.model.refresh_last_if_duplicate(message, color): + return + + self.notification_list_view.blockSignals(True) try: - message, _, priority = self.cli_tracking.popleft() - match priority: - case 1: - self._add_notif_entry(message, "#1A8FBF", self._ICON_INFO) - case 2: - self._add_notif_entry(message, "#E7E147", self._ICON_WARN) - case 3: - self._add_notif_entry(message, "#CA4949", self._ICON_ERROR) - case _: - self._add_notif_entry(message, "#a4a4a4", self._ICON_INFO) + self._add_notif_entry(message, color, icon) self.model.setData(self.model.index(0), True, EntryListModel.EnableRole) finally: - self.update_buttons_list_widget.blockSignals(False) + self.notification_list_view.blockSignals(False) @QtCore.pyqtSlot(ListItem, name="on-item-clicked") def on_item_clicked(self, item: ListItem) -> None: @@ -118,7 +122,7 @@ def on_item_clicked(self, item: ListItem) -> None: @QtCore.pyqtSlot(str, str, int, bool, name="new-notication") def new_notication( self, - origin: str | None = None, + _origin: str | None = None, message: str = "", priority: int = 0, popup: bool = False, @@ -131,8 +135,7 @@ def new_notication( :param popup: sets if notification should appear as popup :type popup: bool """ - self.cli_tracking.append((message, origin, priority)) - self.model.delete_duplicates() + self._ingest_notification(message, priority) if popup: match priority: @@ -147,7 +150,6 @@ def new_notication( self.popup.new_message(message_type=msg_type, message=message, timeout=3000) - self.build_model_list() self.has_new_notification.emit(not self.isVisible()) def _add_notif_entry( @@ -168,13 +170,14 @@ def _add_notif_entry( allow_expand=True, notificate=False, color_left_icon=True, + text_left_padding=10, ) time = QtCore.QDateTime.currentDateTime().toString("hh:mm:ss") item._cache[-1] = time - self.model.add_item(item) + self.model.insert_item(0, item) def _setupUI(self) -> None: - """Setup UI for updatePage""" + """Setup UI for the notification panel""" sizePolicy = QtWidgets.QSizePolicy( QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.MinimumExpanding, @@ -184,16 +187,16 @@ def _setupUI(self) -> None: font = QtGui.QFont() font.setPointSize(20) self.setSizePolicy(sizePolicy) - self.setObjectName("updatePage") + self.setObjectName("notificationPage") self.setStyleSheet( - """#updatePage { + """#notificationPage { background-image: url(:/background/media/1st_background.png); }""" ) self.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) - self.update_page_content_layout = QtWidgets.QVBoxLayout() + self.content_layout = QtWidgets.QVBoxLayout() self.setMinimumSize(800, 480) - self.update_page_content_layout.setContentsMargins(15, 15, 15, 15) + self.content_layout.setContentsMargins(15, 15, 15, 15) self.header_content_layout = QtWidgets.QHBoxLayout() self.header_content_layout.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop) @@ -224,75 +227,75 @@ def _setupUI(self) -> None: self.header_content_layout.addWidget( self.header_title, alignment=QtCore.Qt.AlignmentFlag.AlignCenter ) - self.update_back_btn = IconButton(self) - self.update_back_btn.setMinimumSize(QtCore.QSize(60, 60)) - self.update_back_btn.setMaximumSize(QtCore.QSize(60, 60)) - self.update_back_btn.setFlat(True) - self.update_back_btn.setPixmap(QtGui.QPixmap(":/ui/media/btn_icons/back.svg")) + self.back_btn = IconButton(self) + self.back_btn.setMinimumSize(QtCore.QSize(60, 60)) + self.back_btn.setMaximumSize(QtCore.QSize(60, 60)) + self.back_btn.setFlat(True) + self.back_btn.setPixmap(QtGui.QPixmap(":/ui/media/btn_icons/back.svg")) self.header_content_layout.addWidget( - self.update_back_btn + self.back_btn ) # alignment=QtCore.Qt.AlignmentFlag.AlignCenter) - self.update_page_content_layout.addLayout(self.header_content_layout, 0) + self.content_layout.addLayout(self.header_content_layout, 0) self.main_content_layout = QtWidgets.QHBoxLayout() self.main_content_layout.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) - self.update_buttons_frame = BlocksCustomFrame(self) + self.list_frame = BlocksCustomFrame(self) - self.update_buttons_frame.setMinimumSize(QtCore.QSize(500, 380)) - self.update_buttons_frame.setMaximumSize(QtCore.QSize(560, 500)) + self.list_frame.setMinimumSize(QtCore.QSize(500, 380)) + self.list_frame.setMaximumSize(QtCore.QSize(560, 500)) - self.update_buttons_list_widget = QtWidgets.QListView(self.update_buttons_frame) - self.update_buttons_list_widget.setMouseTracking(True) - self.update_buttons_list_widget.setTabletTracking(True) + self.notification_list_view = QtWidgets.QListView(self.list_frame) + self.notification_list_view.setMouseTracking(True) + self.notification_list_view.setTabletTracking(True) - self.update_buttons_list_widget.setPalette(palette) - self.update_buttons_list_widget.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) - self.update_buttons_list_widget.setStyleSheet("background-color:transparent") - self.update_buttons_list_widget.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken) - self.update_buttons_list_widget.setMinimumSize(self.update_buttons_frame.size()) - self.update_buttons_list_widget.setFrameShape(QtWidgets.QFrame.Shape.NoFrame) - self.update_buttons_list_widget.setVerticalScrollBarPolicy( + self.notification_list_view.setPalette(palette) + self.notification_list_view.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) + self.notification_list_view.setStyleSheet("background-color:transparent") + self.notification_list_view.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken) + self.notification_list_view.setMinimumSize(self.list_frame.size()) + self.notification_list_view.setFrameShape(QtWidgets.QFrame.Shape.NoFrame) + self.notification_list_view.setVerticalScrollBarPolicy( QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff ) - self.update_buttons_list_widget.setHorizontalScrollBarPolicy( + self.notification_list_view.setHorizontalScrollBarPolicy( QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff ) - self.update_buttons_list_widget.setSizeAdjustPolicy( + self.notification_list_view.setSizeAdjustPolicy( QtWidgets.QAbstractScrollArea.SizeAdjustPolicy.AdjustToContents ) - self.update_buttons_list_widget.setAutoScroll(False) - self.update_buttons_list_widget.setProperty("showDropIndicator", False) - self.update_buttons_list_widget.setDefaultDropAction( + self.notification_list_view.setAutoScroll(False) + self.notification_list_view.setProperty("showDropIndicator", False) + self.notification_list_view.setDefaultDropAction( QtCore.Qt.DropAction.IgnoreAction ) - self.update_buttons_list_widget.setAlternatingRowColors(False) - self.update_buttons_list_widget.setSelectionMode( + self.notification_list_view.setAlternatingRowColors(False) + self.notification_list_view.setSelectionMode( QtWidgets.QAbstractItemView.SelectionMode.NoSelection ) - self.update_buttons_list_widget.setSelectionBehavior( + self.notification_list_view.setSelectionBehavior( QtWidgets.QAbstractItemView.SelectionBehavior.SelectItems ) - self.update_buttons_list_widget.setVerticalScrollMode( + self.notification_list_view.setVerticalScrollMode( QtWidgets.QAbstractItemView.ScrollMode.ScrollPerPixel ) - self.update_buttons_list_widget.setHorizontalScrollMode( + self.notification_list_view.setHorizontalScrollMode( QtWidgets.QAbstractItemView.ScrollMode.ScrollPerPixel ) QtWidgets.QScroller.grabGesture( - self.update_buttons_list_widget, + self.notification_list_view, QtWidgets.QScroller.ScrollerGestureType.TouchGesture, ) QtWidgets.QScroller.grabGesture( - self.update_buttons_list_widget, + self.notification_list_view, QtWidgets.QScroller.ScrollerGestureType.LeftMouseButtonGesture, ) - self.update_buttons_layout = QtWidgets.QVBoxLayout() - self.update_buttons_layout.setContentsMargins(0, 0, 0, 0) - self.update_buttons_layout.addWidget(self.update_buttons_list_widget, 0) - self.update_buttons_frame.setLayout(self.update_buttons_layout) + self.list_frame_layout = QtWidgets.QVBoxLayout() + self.list_frame_layout.setContentsMargins(0, 0, 0, 0) + self.list_frame_layout.addWidget(self.notification_list_view, 0) + self.list_frame.setLayout(self.list_frame_layout) - self.main_content_layout.addWidget(self.update_buttons_frame) + self.main_content_layout.addWidget(self.list_frame) self.vlayout = QtWidgets.QVBoxLayout() self.vlayout.setContentsMargins(5, 5, 5, 5) @@ -409,5 +412,5 @@ def _setupUI(self) -> None: self.vlayout.addWidget(self.buttons_frame) self.main_content_layout.addLayout(self.vlayout) - self.update_page_content_layout.addLayout(self.main_content_layout, 1) - self.setLayout(self.update_page_content_layout) + self.content_layout.addLayout(self.main_content_layout, 1) + self.setLayout(self.content_layout) diff --git a/BlocksScreen/lib/utils/list_model.py b/BlocksScreen/lib/utils/list_model.py index a1a6377c..893585bc 100644 --- a/BlocksScreen/lib/utils/list_model.py +++ b/BlocksScreen/lib/utils/list_model.py @@ -13,12 +13,12 @@ class ListItem: _rfontsize: int = 0 _lfontsize: int = 0 - callback: typing.Optional[typing.Callable] = None + callback: typing.Callable | None = None color: str = "#dfdfdf" color_left_icon: bool = False - right_icon: typing.Optional[QtGui.QPixmap] = None - left_icon: typing.Optional[QtGui.QPixmap] = None + right_icon: QtGui.QPixmap | None = None + left_icon: QtGui.QPixmap | None = None selected: bool = False allow_check: bool = True @@ -31,9 +31,10 @@ class ListItem: height: int = 60 notificate: bool = False + text_left_padding: int = 0 # extra gap between the left icon and the text - # stores width and heitgh of the button so we dont need to recalculate it every time - _cache: typing.Dict[int, int] = field(default_factory=dict) + # cached per-width height, plus the -1 key for a notification's display timestamp (str) + _cache: dict[int, typing.Any] = field(default_factory=dict) def clear_cache(self): """Call this if text or font size changes dynamically""" @@ -68,28 +69,17 @@ def remove_item(self, item: ListItem) -> None: self.entries.pop(index) self.endRemoveRows() - def delete_duplicates(self) -> None: - """ - Removes items that have identical text, color, and - last time entry (get(-1)). - """ - seen_identifiers: set[tuple[str, str, str]] = set() - unique_entries: list[ListItem] = [] - - for item in self.entries: - text_val = item.text - color_val = item.color - time_val = item._cache.get(-1) - - identifier = (text_val, color_val, time_val) - - if identifier not in seen_identifiers: - unique_entries.append(item) - seen_identifiers.add(identifier) - - self.beginResetModel() - self.entries = unique_entries - self.endResetModel() + def refresh_last_if_duplicate(self, text: str, color: str) -> bool: + """Collapse a repeat of the most recent entry (O(1)) instead of inserting a new row.""" + if not self.entries: + return False + last = self.entries[0] + if last.text != text or last.color != color: + return False + last._cache[-1] = QtCore.QDateTime.currentDateTime().toString("hh:mm:ss") + idx = self.index(0) + self.dataChanged.emit(idx, idx) + return True def clear(self) -> None: """Clear model rows""" @@ -359,6 +349,7 @@ def sizeHint( left_reserved = 10 if item.left_icon: left_reserved = (base_h * 0.1) + ellipse_size + 8 + left_reserved += item.text_left_padding if item._lfontsize > 0 and item._lfontsize != option.font.pointSize(): f = QtGui.QFont(option.font) @@ -380,8 +371,7 @@ def sizeHint( right_reserved += ellipse_size text_avail_width = target_width - left_reserved - right_reserved - if text_avail_width < 50: - text_avail_width = 50 + text_avail_width = max(text_avail_width, 50) single_line_width = fm.horizontalAdvance(item.text) @@ -499,15 +489,16 @@ def paint( rect.right() - ellipse_size - ellipse_margin - rect.height() * 0.10 ) - text_rect = QtCore.QRectF( + text_left = ( rect.left() + left_margin - + (left_icon_rect.width() if item.left_icon else 0), + + (left_icon_rect.width() if item.left_icon else 0) + + item.text_left_padding + ) + text_rect = QtCore.QRectF( + text_left, rect.top(), - text_margin - - rect.left() - - left_margin - - (left_icon_rect.width() if item.left_icon else 0), + text_margin - text_left, rect.height(), ) diff --git a/tests/util/test_notification_page_unit.py b/tests/util/test_notification_page_unit.py index e0a7553c..e5e41b9c 100644 --- a/tests/util/test_notification_page_unit.py +++ b/tests/util/test_notification_page_unit.py @@ -38,9 +38,9 @@ def _mock_setup(self) -> None: - self.update_buttons_list_widget = QtWidgets.QListView() + self.notification_list_view = QtWidgets.QListView() for attr in ( - "update_back_btn", + "back_btn", "delete_btn", "delete_all_btn", "header_title", From c8590f2310ec4bd15f6e71634ce8dfb7185b0844 Mon Sep 17 00:00:00 2001 From: Guilherme Costa Date: Wed, 9 Sep 2026 12:10:14 +0100 Subject: [PATCH 2/7] refactor(list-model): make icon-text spacing universal across all list-based pages --- BlocksScreen/lib/panels/widgets/notificationPage.py | 1 - BlocksScreen/lib/utils/list_model.py | 9 ++++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/BlocksScreen/lib/panels/widgets/notificationPage.py b/BlocksScreen/lib/panels/widgets/notificationPage.py index bfb29ae3..da7ab5e7 100644 --- a/BlocksScreen/lib/panels/widgets/notificationPage.py +++ b/BlocksScreen/lib/panels/widgets/notificationPage.py @@ -170,7 +170,6 @@ def _add_notif_entry( allow_expand=True, notificate=False, color_left_icon=True, - text_left_padding=10, ) time = QtCore.QDateTime.currentDateTime().toString("hh:mm:ss") item._cache[-1] = time diff --git a/BlocksScreen/lib/utils/list_model.py b/BlocksScreen/lib/utils/list_model.py index 893585bc..28d958a5 100644 --- a/BlocksScreen/lib/utils/list_model.py +++ b/BlocksScreen/lib/utils/list_model.py @@ -3,6 +3,8 @@ from PyQt6 import QtCore, QtGui, QtWidgets # pylint: disable=import-error +_TEXT_LEFT_PADDING = 10 # gap between the left icon and the text, all list pages + @dataclass(slots=True) class ListItem: @@ -31,7 +33,6 @@ class ListItem: height: int = 60 notificate: bool = False - text_left_padding: int = 0 # extra gap between the left icon and the text # cached per-width height, plus the -1 key for a notification's display timestamp (str) _cache: dict[int, typing.Any] = field(default_factory=dict) @@ -348,8 +349,7 @@ def sizeHint( left_reserved = 10 if item.left_icon: - left_reserved = (base_h * 0.1) + ellipse_size + 8 - left_reserved += item.text_left_padding + left_reserved = (base_h * 0.1) + ellipse_size + 8 + _TEXT_LEFT_PADDING if item._lfontsize > 0 and item._lfontsize != option.font.pointSize(): f = QtGui.QFont(option.font) @@ -492,8 +492,7 @@ def paint( text_left = ( rect.left() + left_margin - + (left_icon_rect.width() if item.left_icon else 0) - + item.text_left_padding + + (left_icon_rect.width() + _TEXT_LEFT_PADDING if item.left_icon else 0) ) text_rect = QtCore.QRectF( text_left, From ecb87c60f96890c7a63941e9e1e5cd479bfc030c Mon Sep 17 00:00:00 2001 From: Guilherme Costa Date: Wed, 9 Sep 2026 12:20:23 +0100 Subject: [PATCH 3/7] fix(ui): keep expand-toggle clicks selecting the item; make icon-text padding universal --- BlocksScreen/lib/utils/list_model.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/BlocksScreen/lib/utils/list_model.py b/BlocksScreen/lib/utils/list_model.py index 28d958a5..8cc3b14a 100644 --- a/BlocksScreen/lib/utils/list_model.py +++ b/BlocksScreen/lib/utils/list_model.py @@ -608,6 +608,16 @@ def editorEvent( # pylint: disable=invalid-name ): new_state = not item.is_expanded model.setData(index, new_state, EntryListModel.ExpandRole) + # Toggling expand must also select — the arrow covers most of + # the row, so a tap there would otherwise silently skip + # selection and leave the info panel stale (first-click bug). + if self.prev_index != index.row(): + prev_index: QtCore.QModelIndex = model.index(self.prev_index) + if prev_index.isValid(): + model.setData(prev_index, False, EntryListModel.EnableRole) + self.prev_index = index.row() + model.setData(index, True, EntryListModel.EnableRole) + self.item_selected.emit(item) return True if self.prev_index != index.row(): From af3e897887ba4fdb68e071c92e8fb5c124139289 Mon Sep 17 00:00:00 2001 From: Guilherme Costa Date: Wed, 9 Sep 2026 12:30:01 +0100 Subject: [PATCH 4/7] fix(notifications): keep info panel in sync with selection (expand-click, dedupe refresh, delete/reset), add universal icon-text padding --- .../lib/panels/widgets/notificationPage.py | 31 +++++++++++++++++-- BlocksScreen/lib/utils/list_model.py | 1 + 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/BlocksScreen/lib/panels/widgets/notificationPage.py b/BlocksScreen/lib/panels/widgets/notificationPage.py index da7ab5e7..8f65ad15 100644 --- a/BlocksScreen/lib/panels/widgets/notificationPage.py +++ b/BlocksScreen/lib/panels/widgets/notificationPage.py @@ -53,14 +53,19 @@ def show_notification_panel( self.show() self.raise_() self.has_new_notification.emit(False) + if self.model.entries: + self._select_row(0) def delete_selected_item(self) -> None: """Deletes currently selected item from the list view""" if self.selected_item is None: return self.model.remove_item(self.selected_item) - self.delete_btn.setEnabled(False) self.selected_item = None + if self.model.entries: + self._select_row(0) + else: + self._clear_info_box() def reset_view_model(self) -> None: """Clears items from ListView @@ -68,8 +73,16 @@ def reset_view_model(self) -> None: """ self.model.clear() self.entry_delegate.clear() + self.selected_item = None + self._clear_info_box() self.has_new_notification.emit(False) + def _clear_info_box(self) -> None: + """Resets the info box to its empty-list default (no item selected).""" + self.delete_btn.setEnabled(False) + self.type_label.setText("N/A") + self.time_label.setText("N/A") + def _on_rows_inserted( self, _parent: QtCore.QModelIndex, first: int, _last: int ) -> None: @@ -77,6 +90,19 @@ def _on_rows_inserted( if first <= self.entry_delegate.prev_index: self.entry_delegate.prev_index += 1 + def _select_row(self, row: int) -> None: + """Selects *row*, clearing the previous selection and refreshing the info box.""" + index = self.model.index(row) + if not index.isValid(): + return + if self.entry_delegate.prev_index != row: + prev_index = self.model.index(self.entry_delegate.prev_index) + if prev_index.isValid(): + self.model.setData(prev_index, False, EntryListModel.EnableRole) + self.entry_delegate.prev_index = row + self.model.setData(index, True, EntryListModel.EnableRole) + self.on_item_clicked(index.data(QtCore.Qt.ItemDataRole.UserRole)) + def _ingest_notification(self, message: str, priority: int) -> None: """Adds *message* to the model, collapsing a repeat of the last entry (moonraker echo spam).""" match priority: @@ -90,12 +116,13 @@ def _ingest_notification(self, message: str, priority: int) -> None: color, icon = "#a4a4a4", self._ICON_INFO if self.model.refresh_last_if_duplicate(message, color): + self._select_row(0) return self.notification_list_view.blockSignals(True) try: self._add_notif_entry(message, color, icon) - self.model.setData(self.model.index(0), True, EntryListModel.EnableRole) + self._select_row(0) finally: self.notification_list_view.blockSignals(False) diff --git a/BlocksScreen/lib/utils/list_model.py b/BlocksScreen/lib/utils/list_model.py index 8cc3b14a..437a2bfa 100644 --- a/BlocksScreen/lib/utils/list_model.py +++ b/BlocksScreen/lib/utils/list_model.py @@ -264,6 +264,7 @@ def setData(self, index: QtCore.QModelIndex, value: typing.Any, role: int) -> bo item.is_expanded = value self.layoutChanged.emit() self.dataChanged.emit(index, index, [EntryListModel.ExpandRole]) + return True if role == QtCore.Qt.ItemDataRole.UserRole: self.dataChanged.emit(index, index, [QtCore.Qt.ItemDataRole.UserRole]) return True From ade057b31dae9d98075b00bc29da74c4113c8738 Mon Sep 17 00:00:00 2001 From: Guilherme Costa Date: Wed, 9 Sep 2026 15:27:26 +0100 Subject: [PATCH 5/7] fix(ui): center notification icon with other header icons --- .../lib/panels/widgets/notificationPage.py | 25 ++++++++----------- BlocksScreen/lib/ui/mainWindow.ui | 2 +- BlocksScreen/lib/ui/mainWindow_ui.py | 2 +- 3 files changed, 12 insertions(+), 17 deletions(-) diff --git a/BlocksScreen/lib/panels/widgets/notificationPage.py b/BlocksScreen/lib/panels/widgets/notificationPage.py index 8f65ad15..df92fc2b 100644 --- a/BlocksScreen/lib/panels/widgets/notificationPage.py +++ b/BlocksScreen/lib/panels/widgets/notificationPage.py @@ -332,36 +332,31 @@ def _setupUI(self) -> None: self.info_box_layout = QtWidgets.QGridLayout(self.info_frame) self.info_box_layout.setContentsMargins(0, 0, 0, 0) - self.info_box_layout.addItem( - QtWidgets.QSpacerItem( - 20, - 20, - QtWidgets.QSizePolicy.Policy.Minimum, - QtWidgets.QSizePolicy.Policy.Minimum, - ), - 0, - 0, - ) - self.type_title = QtWidgets.QLabel(self.info_frame) self.type_title.setText("Type:") self.type_title.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) - self.info_box_layout.addWidget(self.type_title, 1, 0) + self.info_box_layout.addWidget(self.type_title, 1, 1) self.type_label = QtWidgets.QLabel(self.info_frame) self.type_label.setText("N/A") self.type_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) - self.info_box_layout.addWidget(self.type_label, 1, 1) + self.info_box_layout.addWidget(self.type_label, 1, 2) self.time_title = QtWidgets.QLabel(self.info_frame) self.time_title.setText("Time:") self.time_title.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) - self.info_box_layout.addWidget(self.time_title, 2, 0) + self.info_box_layout.addWidget(self.time_title, 2, 1) self.time_label = QtWidgets.QLabel(self.info_frame) self.time_label.setText("N/A") self.time_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) - self.info_box_layout.addWidget(self.time_label, 2, 1) + self.info_box_layout.addWidget(self.time_label, 2, 2) + + # Stretch columns/rows surrounding the content block to center it in the frame. + self.info_box_layout.setColumnStretch(0, 1) + self.info_box_layout.setColumnStretch(3, 1) + self.info_box_layout.setRowStretch(0, 1) + self.info_box_layout.setRowStretch(3, 1) self.type_title.setFont(font) self.type_title.setStyleSheet("color:#FFFFFF") diff --git a/BlocksScreen/lib/ui/mainWindow.ui b/BlocksScreen/lib/ui/mainWindow.ui index 451677f2..f73a9794 100644 --- a/BlocksScreen/lib/ui/mainWindow.ui +++ b/BlocksScreen/lib/ui/mainWindow.ui @@ -531,7 +531,7 @@ QPushButton:pressed{ 0 - + diff --git a/BlocksScreen/lib/ui/mainWindow_ui.py b/BlocksScreen/lib/ui/mainWindow_ui.py index e467dacf..259b6367 100644 --- a/BlocksScreen/lib/ui/mainWindow_ui.py +++ b/BlocksScreen/lib/ui/mainWindow_ui.py @@ -239,7 +239,7 @@ def setupUi(self, MainWindow): self.notification_btn.setFlat(True) self.notification_btn.setProperty("icon_pixmap", QtGui.QPixmap(":/ui/media/btn_icons/notification.svg")) self.notification_btn.setObjectName("notification_btn") - self.header_main_layout.addWidget(self.notification_btn, 0, QtCore.Qt.AlignmentFlag.AlignLeft) + self.header_main_layout.addWidget(self.notification_btn, 0, QtCore.Qt.AlignmentFlag.AlignHCenter) self.extruder_temp_display = DisplayButton(parent=self.main_header_layout) self.extruder_temp_display.setMinimumSize(QtCore.QSize(140, 60)) self.extruder_temp_display.setMaximumSize(QtCore.QSize(160, 60)) From 489a1bdab2b0b03abb410baf97a6339552e31a53 Mon Sep 17 00:00:00 2001 From: Guilherme Costa Date: Wed, 9 Sep 2026 15:34:01 +0100 Subject: [PATCH 6/7] fix(ui): remove size-policy override to align header icons, restore info-box row spacing --- BlocksScreen/lib/panels/widgets/notificationPage.py | 4 +++- BlocksScreen/lib/ui/mainWindow.ui | 6 ------ BlocksScreen/lib/ui/mainWindow_ui.py | 5 ----- 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/BlocksScreen/lib/panels/widgets/notificationPage.py b/BlocksScreen/lib/panels/widgets/notificationPage.py index df92fc2b..7440c25b 100644 --- a/BlocksScreen/lib/panels/widgets/notificationPage.py +++ b/BlocksScreen/lib/panels/widgets/notificationPage.py @@ -352,10 +352,12 @@ def _setupUI(self) -> None: self.time_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) self.info_box_layout.addWidget(self.time_label, 2, 2) - # Stretch columns/rows surrounding the content block to center it in the frame. + # Equal stretch keeps the Type/Time gap instead of collapsing it into the padding. self.info_box_layout.setColumnStretch(0, 1) self.info_box_layout.setColumnStretch(3, 1) self.info_box_layout.setRowStretch(0, 1) + self.info_box_layout.setRowStretch(1, 1) + self.info_box_layout.setRowStretch(2, 1) self.info_box_layout.setRowStretch(3, 1) self.type_title.setFont(font) diff --git a/BlocksScreen/lib/ui/mainWindow.ui b/BlocksScreen/lib/ui/mainWindow.ui index f73a9794..79af7de2 100644 --- a/BlocksScreen/lib/ui/mainWindow.ui +++ b/BlocksScreen/lib/ui/mainWindow.ui @@ -533,12 +533,6 @@ QPushButton:pressed{ - - - 1 - 1 - - 60 diff --git a/BlocksScreen/lib/ui/mainWindow_ui.py b/BlocksScreen/lib/ui/mainWindow_ui.py index 259b6367..3e9e3a35 100644 --- a/BlocksScreen/lib/ui/mainWindow_ui.py +++ b/BlocksScreen/lib/ui/mainWindow_ui.py @@ -227,11 +227,6 @@ def setupUi(self, MainWindow): self.header_main_layout.setSpacing(10) self.header_main_layout.setObjectName("header_main_layout") self.notification_btn = IconButton(parent=self.main_header_layout) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Fixed) - sizePolicy.setHorizontalStretch(1) - sizePolicy.setVerticalStretch(1) - sizePolicy.setHeightForWidth(self.notification_btn.sizePolicy().hasHeightForWidth()) - self.notification_btn.setSizePolicy(sizePolicy) self.notification_btn.setMinimumSize(QtCore.QSize(60, 60)) self.notification_btn.setMaximumSize(QtCore.QSize(60, 60)) self.notification_btn.setText("") From f5b472bfe9ff21e4f71508f10e137bd63d89d353 Mon Sep 17 00:00:00 2001 From: Guilherme Costa Date: Wed, 9 Sep 2026 15:37:46 +0100 Subject: [PATCH 7/7] fix(notifications): widen Type/Time gap in info box via weighted row stretch --- BlocksScreen/lib/panels/widgets/notificationPage.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/BlocksScreen/lib/panels/widgets/notificationPage.py b/BlocksScreen/lib/panels/widgets/notificationPage.py index 7440c25b..7664efa9 100644 --- a/BlocksScreen/lib/panels/widgets/notificationPage.py +++ b/BlocksScreen/lib/panels/widgets/notificationPage.py @@ -352,12 +352,12 @@ def _setupUI(self) -> None: self.time_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) self.info_box_layout.addWidget(self.time_label, 2, 2) - # Equal stretch keeps the Type/Time gap instead of collapsing it into the padding. + # Rows 1/2 outweigh the outer padding so most slack goes into the Type/Time gap. self.info_box_layout.setColumnStretch(0, 1) self.info_box_layout.setColumnStretch(3, 1) self.info_box_layout.setRowStretch(0, 1) - self.info_box_layout.setRowStretch(1, 1) - self.info_box_layout.setRowStretch(2, 1) + self.info_box_layout.setRowStretch(1, 2) + self.info_box_layout.setRowStretch(2, 2) self.info_box_layout.setRowStretch(3, 1) self.type_title.setFont(font)