Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions BlocksScreen/lib/panels/mainWindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,12 @@ def __init__(self):
self.utilitiesPanel.update_available.connect(self.on_update_available)

self.ui.notification_btn.clicked.connect(self.notiPage.show_notification_panel)
self.notiPage.has_new_notification.connect(
self.ui.notification_btn.setShowNotification
)
self.notiPage.has_new_notification.connect(
self.conn_window.notification_button.setShowNotification
)
self.ui.extruder_temp_display.clicked.connect(
lambda: self.global_change_page(
self.ui.main_content_widget.indexOf(self.ui.controlTab),
Expand Down
6 changes: 6 additions & 0 deletions BlocksScreen/lib/panels/widgets/notificationPage.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ class NotificationPage(QtWidgets.QWidget):
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"
)

def __init__(self, parent=None) -> None:
super().__init__(parent)
Expand Down Expand Up @@ -56,6 +59,7 @@ def show_notification_panel(
self.update()
self.show()
self.raise_()
self.has_new_notification.emit(False)
Comment thread
gmmcosta15 marked this conversation as resolved.

def delete_selected_item(self) -> None:
"""Deletes currently selected item from the list view"""
Expand All @@ -71,6 +75,7 @@ def reset_view_model(self) -> None:
"""
self.model.clear()
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"""
Expand Down Expand Up @@ -143,6 +148,7 @@ 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(
self,
Expand Down
21 changes: 21 additions & 0 deletions BlocksScreen/lib/utils/icon_button.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import typing

from PyQt6 import QtCore, QtGui, QtWidgets

NOTIFICATION_DOT_COLOR = QtGui.QColor(226, 31, 31)


class IconButton(QtWidgets.QPushButton):
def __init__(self, parent: QtWidgets.QWidget = None) -> None:
Expand All @@ -12,9 +15,16 @@ def __init__(self, parent: QtWidgets.QWidget = None) -> None:
self._text: str = ""
self._name: str = ""
self.text_color: QtGui.QColor = QtGui.QColor(255, 255, 255)
self._show_notification: bool = False
self.setAttribute(QtCore.Qt.WidgetAttribute.WA_AcceptTouchEvents, True)
self.pressed_bg_color = QtGui.QColor(223, 223, 223, 70) # Set to solid white

def setShowNotification(self, show: bool) -> None:
"""Set notification dot on button"""
if self._show_notification != show:
self._show_notification = show
self.update()

@property
def name(self):
"""Widget name"""
Expand Down Expand Up @@ -124,8 +134,19 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None:
str(self.text()),
)

if self._show_notification:
self._paint_notification(painter)

painter.end()

def _paint_notification(self, painter: QtGui.QPainter) -> None:
"""Draw the unread-notification dot in the top-right corner"""
dot_diameter = min(10, self.height() * 0.25)
dot_x = self.width() - dot_diameter
painter.setBrush(NOTIFICATION_DOT_COLOR)
painter.setPen(QtCore.Qt.PenStyle.NoPen)
painter.drawEllipse(QtCore.QRectF(dot_x, 0, dot_diameter, dot_diameter))

def setProperty(self, name: str, value: typing.Any) -> bool:
"""Re-implemented method, set widget properties"""
if name == "icon_pixmap":
Expand Down
89 changes: 89 additions & 0 deletions tests/util/test_notification_page_unit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
"""Unit tests for NotificationPage's unread-notification dot signal."""

import importlib.machinery
import importlib.util
import sys
from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest
from PyQt6 import QtWidgets

# tests/panels/conftest.py and tests/network/conftest.py stub sys.modules
# entries under "lib.*" with fakes (no teardown) when collected in the same
# xdist worker; evict notificationPage's whole dependency closure first so
# we load the real BlocksScreen/lib modules instead of another file's stubs.
for _stale_key in (
"lib",
"lib.panels",
"lib.panels.widgets",
"lib.panels.widgets.notificationPage",
"lib.panels.widgets.popupDialogWidget",
"lib.utils",
"lib.utils.blocks_button",
"lib.utils.blocks_frame",
"lib.utils.icon_button",
"lib.utils.list_model",
):
sys.modules.pop(_stale_key, None)

_bs_lib_dir = Path(__file__).resolve().parent.parent.parent / "BlocksScreen" / "lib"
_lib_spec = importlib.machinery.ModuleSpec("lib", loader=None, is_package=True)
_lib_spec.submodule_search_locations = [str(_bs_lib_dir)]
sys.modules["lib"] = importlib.util.module_from_spec(_lib_spec)

from lib.panels.widgets.notificationPage import NotificationPage # noqa: E402

_notification_page_module = sys.modules[NotificationPage.__module__]


def _mock_setup(self) -> None:
self.update_buttons_list_widget = QtWidgets.QListView()
for attr in (
"update_back_btn",
"delete_btn",
"delete_all_btn",
"header_title",
"type_label",
"time_label",
):
setattr(self, attr, MagicMock())


@pytest.fixture
def page(qtbot):
with (
patch.object(_notification_page_module, "Popup", MagicMock()),
patch.object(NotificationPage, "_setupUI", _mock_setup),
):
pg = NotificationPage()
qtbot.addWidget(pg)
return pg


def test_new_notification_sets_dot(page, qtbot):
with qtbot.waitSignal(page.has_new_notification, timeout=200) as blocker:
page.new_notication("test", "hello", 1, False)
assert blocker.args == [True]


def test_show_notification_panel_clears_dot(page, qtbot):
# show_notification_panel() no-ops without a parent; stub one out.
page.parent = MagicMock(return_value=MagicMock())
with qtbot.waitSignal(page.has_new_notification, timeout=200) as blocker:
page.show_notification_panel()
assert blocker.args == [False]


def test_new_notification_while_open_does_not_set_dot(page, qtbot):
page.show()
with qtbot.waitSignal(page.has_new_notification, timeout=200) as blocker:
page.new_notication("test", "hello", 1, False)
assert blocker.args == [False]


def test_reset_view_model_clears_dot(page, qtbot):
page.new_notication("test", "hello", 1, False)
with qtbot.waitSignal(page.has_new_notification, timeout=200) as blocker:
page.reset_view_model()
assert blocker.args == [False]
Loading