From 86e07a513422a757b8797ab2f698c38537e68ce0 Mon Sep 17 00:00:00 2001 From: Ro Date: Mon, 16 Sep 2024 12:58:24 -0400 Subject: [PATCH] Move Top Pane to Bottom and rename. See discussion at . Refs #2160. --- client/securedrop_client/gui/main.py | 28 ++++---- client/securedrop_client/gui/widgets.py | 6 +- .../functional/test_offline_delete_source.py | 2 +- .../functional/test_offline_star_source.py | 2 +- client/tests/gui/test_main.py | 44 ++++++------ client/tests/gui/test_widgets.py | 72 +++++++++---------- .../tests/integration/test_styles_sdclient.py | 16 ++--- 7 files changed, 85 insertions(+), 85 deletions(-) diff --git a/client/securedrop_client/gui/main.py b/client/securedrop_client/gui/main.py index 537aea87e..97e7c0fd6 100644 --- a/client/securedrop_client/gui/main.py +++ b/client/securedrop_client/gui/main.py @@ -28,7 +28,7 @@ from securedrop_client import __version__, state from securedrop_client.db import Source, User from securedrop_client.gui.auth import LoginDialog -from securedrop_client.gui.widgets import LeftPane, MainView, TopPane +from securedrop_client.gui.widgets import BottomPane, LeftPane, MainView from securedrop_client.logic import Controller from securedrop_client.resources import load_all_fonts, load_css, load_icon @@ -51,10 +51,10 @@ def __init__( Create the default start state. The window contains a root widget into which is placed: - * A status bar widget at the top, containing curent user / status - information. * A main-view widget, itself containing a list view for sources and a place for details / message contents / forms. + * A status bar widget at the bottom, containing network and error status + information. """ super().__init__() load_all_fonts() @@ -62,8 +62,8 @@ def __init__( self.setWindowTitle(_("SecureDrop Client {}").format(__version__)) self.setWindowIcon(load_icon(self.icon)) - # Top Pane to display activity and error messages - self.top_pane = TopPane() + # Bottom Pane to display activity and error messages + self.bottom_pane = BottomPane() # Main Pane to display everything else self.main_pane = QWidget() @@ -77,15 +77,15 @@ def __init__( layout.addWidget(self.left_pane) layout.addWidget(self.main_view) - # Set the main window's central widget to show Top Pane and Main Pane + # Set the main window's central widget to show Main Pane and Bottom Pane self.central_widget = QWidget() central_widget_layout = QVBoxLayout() central_widget_layout.setContentsMargins(0, 0, 0, 0) central_widget_layout.setSpacing(0) self.central_widget.setLayout(central_widget_layout) self.setCentralWidget(self.central_widget) - central_widget_layout.addWidget(self.top_pane) central_widget_layout.addWidget(self.main_pane) + central_widget_layout.addWidget(self.bottom_pane) # Dialogs self.login_dialog: LoginDialog | None = None @@ -103,7 +103,7 @@ def setup(self, controller: Controller) -> None: views used in the UI. """ self.controller = controller - self.top_pane.setup(self.controller) + self.bottom_pane.setup(self.controller) self.left_pane.setup(self, self.controller) self.main_view.setup(self.controller) self.show_login() @@ -182,41 +182,41 @@ def set_logged_in_as(self, db_user: User) -> None: Update the UI to show user logged in with username. """ self.left_pane.set_logged_in_as(db_user) - self.top_pane.set_logged_in() + self.bottom_pane.set_logged_in() def logout(self) -> None: """ Update the UI to show the user is logged out. """ self.left_pane.set_logged_out() - self.top_pane.set_logged_out() + self.bottom_pane.set_logged_out() def update_sync_status(self, message: str, duration: int = 0) -> None: """ Display an activity status message to the user. Optionally, supply a duration (in milliseconds), the default will continuously show the message. """ - self.top_pane.update_sync_status(message, duration) + self.bottom_pane.update_sync_status(message, duration) def update_activity_status(self, message: str, duration: int = 0) -> None: """ Display an activity status message to the user. Optionally, supply a duration (in milliseconds), the default will continuously show the message. """ - self.top_pane.update_activity_status(message, duration) + self.bottom_pane.update_activity_status(message, duration) def update_error_status(self, message: str, duration: int = 10000) -> None: """ Display an error status message to the user. Optionally, supply a duration (in milliseconds), the default will continuously show the message. """ - self.top_pane.update_error_status(message, duration) + self.bottom_pane.update_error_status(message, duration) def clear_error_status(self) -> None: """ Clear any message currently in the error status bar. """ - self.top_pane.clear_error_status() + self.bottom_pane.clear_error_status() def clear_clipboard(self) -> None: """ diff --git a/client/securedrop_client/gui/widgets.py b/client/securedrop_client/gui/widgets.py index 07c9949e1..5421e67ad 100644 --- a/client/securedrop_client/gui/widgets.py +++ b/client/securedrop_client/gui/widgets.py @@ -97,9 +97,9 @@ NO_DELAY = 1 -class TopPane(QWidget): +class BottomPane(QWidget): """ - Top pane of the app window. + Bottom pane of the app window. """ def __init__(self) -> None: @@ -155,7 +155,7 @@ def __init__(self) -> None: activity_status_bar_spacer = QWidget() - # Set height of top pane to 42 pixels + # Set height of bottom pane to 42 pixels self.setFixedHeight(42) self.sync_icon.setFixedHeight(42) self.activity_status_bar.setFixedHeight(42) diff --git a/client/tests/functional/test_offline_delete_source.py b/client/tests/functional/test_offline_delete_source.py index f3fc74a83..1a108752e 100644 --- a/client/tests/functional/test_offline_delete_source.py +++ b/client/tests/functional/test_offline_delete_source.py @@ -44,7 +44,7 @@ def check_for_conversation(): controller.delete_source(conversation.conversation_title_bar.source) def check_for_error(): - msg = gui.top_pane.error_status_bar.status_bar.currentMessage() + msg = gui.bottom_pane.error_status_bar.status_bar.currentMessage() assert msg == "You must sign in to perform this action." qtbot.waitUntil(check_for_error, timeout=TIME_CLICK_ACTION) diff --git a/client/tests/functional/test_offline_star_source.py b/client/tests/functional/test_offline_star_source.py index 8989c57f1..947c54aeb 100644 --- a/client/tests/functional/test_offline_star_source.py +++ b/client/tests/functional/test_offline_star_source.py @@ -39,7 +39,7 @@ def check_for_sources(): qtbot.mouseClick(first_source_widget.star, Qt.LeftButton) def sign_in_required_error(): - msg = gui.top_pane.error_status_bar.status_bar.currentMessage() + msg = gui.bottom_pane.error_status_bar.status_bar.currentMessage() assert msg == "You must sign in to perform this action." qtbot.waitUntil(sign_in_required_error, timeout=TIME_RENDER_CONV_VIEW) diff --git a/client/tests/gui/test_main.py b/client/tests/gui/test_main.py index 411429e1f..7f48897b0 100644 --- a/client/tests/gui/test_main.py +++ b/client/tests/gui/test_main.py @@ -55,7 +55,7 @@ def test_setup(mocker, homedir, session_maker): """ w = Window() w.show_login = mocker.MagicMock() - w.top_pane = mocker.MagicMock() + w.bottom_pane = mocker.MagicMock() w.left_pane = mocker.MagicMock() w.main_view = mocker.MagicMock() controller = Controller("http://localhost", mocker.MagicMock(), session_maker, homedir, None) @@ -63,7 +63,7 @@ def test_setup(mocker, homedir, session_maker): w.setup(controller) assert w.controller == controller - w.top_pane.setup.assert_called_once_with(controller) + w.bottom_pane.setup.assert_called_once_with(controller) w.left_pane.setup.assert_called_once_with(w, controller) w.main_view.setup.assert_called_once_with(controller) w.show_login.assert_called_once_with() @@ -270,46 +270,46 @@ def test_show_sources(mocker): def test_update_error_status_default(mocker): """ - Ensure that the error to be shown in the error status bar will be passed to the top pane with a - default duration of 10 seconds. + Ensure that the error to be shown in the error status bar will be passed + to the bottom pane with a default duration of 10 seconds. """ w = Window() - w.top_pane = mocker.MagicMock() + w.bottom_pane = mocker.MagicMock() w.update_error_status(message="test error message") - w.top_pane.update_error_status.assert_called_once_with("test error message", 10000) + w.bottom_pane.update_error_status.assert_called_once_with("test error message", 10000) def test_update_error_status(mocker): """ - Ensure that the error to be shown in the error status bar will be passed to the top pane with - the duration of seconds provided. + Ensure that the error to be shown in the error status bar will be passed to the + bottom pane with the duration of seconds provided. """ w = Window() - w.top_pane = mocker.MagicMock() + w.bottom_pane = mocker.MagicMock() w.update_error_status(message="test error message", duration=123) - w.top_pane.update_error_status.assert_called_once_with("test error message", 123) + w.bottom_pane.update_error_status.assert_called_once_with("test error message", 123) def test_update_activity_status_default(mocker): """ - Ensure that the activity to be shown in the activity status bar will be passed to the top pane - with a default duration of 0 seconds, i.e. forever. + Ensure that the activity to be shown in the activity status bar will be passed to the + bottom pane with a default duration of 0 seconds, i.e. forever. """ w = Window() - w.top_pane = mocker.MagicMock() + w.bottom_pane = mocker.MagicMock() w.update_activity_status(message="test message") - w.top_pane.update_activity_status.assert_called_once_with("test message", 0) + w.bottom_pane.update_activity_status.assert_called_once_with("test message", 0) def test_update_activity_status(mocker): """ - Ensure that the activity to be shown in the activity status bar will be passed to the top pane - with the duration of seconds provided. + Ensure that the activity to be shown in the activity status bar will be passed to the + bottom pane with the duration of seconds provided. """ w = Window() - w.top_pane = mocker.MagicMock() + w.bottom_pane = mocker.MagicMock() w.update_activity_status(message="test message", duration=123) - w.top_pane.update_activity_status.assert_called_once_with("test message", 123) + w.bottom_pane.update_activity_status.assert_called_once_with("test message", 123) def test_clear_error_status(mocker): @@ -317,11 +317,11 @@ def test_clear_error_status(mocker): Ensure clear_error_status is called. """ w = Window() - w.top_pane = mocker.MagicMock() + w.bottom_pane = mocker.MagicMock() w.clear_error_status() - w.top_pane.clear_error_status.assert_called_once_with() + w.bottom_pane.clear_error_status.assert_called_once_with() def test_show_last_sync(mocker): @@ -363,9 +363,9 @@ def test_logout(mocker): """ w = Window() w.left_pane = mocker.MagicMock() - w.top_pane = mocker.MagicMock() + w.bottom_pane = mocker.MagicMock() w.logout() w.left_pane.set_logged_out.assert_called_once_with() - w.top_pane.set_logged_out.assert_called_once_with() + w.bottom_pane.set_logged_out.assert_called_once_with() diff --git a/client/tests/gui/test_widgets.py b/client/tests/gui/test_widgets.py index e72b7fd4f..7ec3592af 100644 --- a/client/tests/gui/test_widgets.py +++ b/client/tests/gui/test_widgets.py @@ -22,6 +22,7 @@ from securedrop_client.gui.source import DeleteSourceDialog from securedrop_client.gui.widgets import ( ActivityStatusBar, + BottomPane, ConversationView, EmptyConversationView, ErrorStatusBar, @@ -46,7 +47,6 @@ SpeechBubble, StarToggleButton, SyncIcon, - TopPane, UserButton, UserIconLabel, UserMenu, @@ -55,87 +55,87 @@ from tests import factory -def test_TopPane_init(mocker): +def test_BottomPane_init(mocker): """ - Ensure the TopPane instance is correctly set up. + Ensure the BottomPane instance is correctly set up. """ - tp = TopPane() - file_path = tp.sync_icon.sync_animation.fileName() + bp = BottomPane() + file_path = bp.sync_icon.sync_animation.fileName() filename = file_path[file_path.rfind("/") + 1 :] assert filename == "sync_disabled.gif" -def test_TopPane_setup(mocker): +def test_BottomPane_setup(mocker): """ Calling setup calls setup for SyncIcon. """ - tp = TopPane() - tp.sync_icon = mocker.MagicMock() + bp = BottomPane() + bp.sync_icon = mocker.MagicMock() mock_controller = mocker.MagicMock() - tp.setup(mock_controller) + bp.setup(mock_controller) - tp.sync_icon.setup.assert_called_once_with(mock_controller) + bp.sync_icon.setup.assert_called_once_with(mock_controller) -def test_TopPane_set_logged_in(mocker): +def test_BottomPane_set_logged_in(mocker): """ - Calling set_logged_in calls enable on TopPane. + Calling set_logged_in calls enable on BottomPane. """ - tp = TopPane() - tp.sync_icon = mocker.MagicMock() + bp = BottomPane() + bp.sync_icon = mocker.MagicMock() - tp.set_logged_in() + bp.set_logged_in() - tp.sync_icon.enable.assert_called_once_with() + bp.sync_icon.enable.assert_called_once_with() -def test_TopPane_set_logged_out(mocker): +def test_BottomPane_set_logged_out(mocker): """ Calling set_logged_out calls disable on SyncIcon. """ - tp = TopPane() - tp.sync_icon = mocker.MagicMock() + bp = BottomPane() + bp.sync_icon = mocker.MagicMock() - tp.set_logged_out() + bp.set_logged_out() - tp.sync_icon.disable.assert_called_once_with() + bp.sync_icon.disable.assert_called_once_with() -def test_TopPane_update_activity_status(mocker): +def test_BottomPane_update_activity_status(mocker): """ Calling update_activity_status calls update_message on ActivityStatusBar. """ - tp = TopPane() - tp.activity_status_bar = mocker.MagicMock() + bp = BottomPane() + bp.activity_status_bar = mocker.MagicMock() - tp.update_activity_status(message="test message", duration=5) + bp.update_activity_status(message="test message", duration=5) - tp.activity_status_bar.update_message.assert_called_once_with("test message", 5) + bp.activity_status_bar.update_message.assert_called_once_with("test message", 5) -def test_TopPane_update_error_status(mocker): +def test_BottomPane_update_error_status(mocker): """ Calling update_error_status calls update_message on ErrorStatusBar. """ - tp = TopPane() - tp.error_status_bar = mocker.MagicMock() + bp = BottomPane() + bp.error_status_bar = mocker.MagicMock() - tp.update_error_status(message="test message", duration=5) + bp.update_error_status(message="test message", duration=5) - tp.error_status_bar.update_message.assert_called_once_with("test message", 5) + bp.error_status_bar.update_message.assert_called_once_with("test message", 5) -def test_TopPane_clear_error_status(mocker): +def test_BottomPane_clear_error_status(mocker): """ Calling clear_error_status calls clear_message. """ - tp = TopPane() - tp.error_status_bar = mocker.MagicMock() + bp = BottomPane() + bp.error_status_bar = mocker.MagicMock() - tp.clear_error_status() + bp.clear_error_status() - tp.error_status_bar.clear_message.assert_called_once_with() + bp.error_status_bar.clear_message.assert_called_once_with() def test_LeftPane_init(mocker): diff --git a/client/tests/integration/test_styles_sdclient.py b/client/tests/integration/test_styles_sdclient.py index c5e2dc7d3..cab18faa8 100644 --- a/client/tests/integration/test_styles_sdclient.py +++ b/client/tests/integration/test_styles_sdclient.py @@ -34,14 +34,14 @@ def test_class_name_matches_css_object_name(mocker, main_window): assert "LoginErrorBar" in login_error_bar.error_icon.objectName() assert "LoginErrorBar" in login_error_bar.error_status_bar.objectName() - # Top Pane - sync_icon = main_window.top_pane.sync_icon + # Bottom Pane + sync_icon = main_window.bottom_pane.sync_icon assert sync_icon.__class__.__name__ == "SyncIcon" assert sync_icon.objectName() == "SyncIcon" - activity_status_bar = main_window.top_pane.activity_status_bar + activity_status_bar = main_window.bottom_pane.activity_status_bar assert activity_status_bar.__class__.__name__ == "ActivityStatusBar" assert activity_status_bar.objectName() == "ActivityStatusBar" - error_status_bar = main_window.top_pane.error_status_bar + error_status_bar = main_window.bottom_pane.error_status_bar assert error_status_bar.__class__.__name__ == "ErrorStatusBar" assert "ErrorStatusBar" in error_status_bar.vertical_bar.objectName() assert "ErrorStatusBar" in error_status_bar.label.objectName() @@ -188,16 +188,16 @@ def test_styles_for_login_dialog(mocker, main_window): assert login_error_bar.error_status_bar.palette().color(QPalette.Foreground).name() == "#ffffff" -def test_styles_for_top_pane(mocker, main_window): - sync_icon = main_window.top_pane.sync_icon +def test_styles_for_bottom_pane(mocker, main_window): + sync_icon = main_window.bottom_pane.sync_icon assert sync_icon.palette().color(QPalette.Base).name() == "#ffffff" - activity_status_bar = main_window.top_pane.activity_status_bar + activity_status_bar = main_window.bottom_pane.activity_status_bar assert activity_status_bar.font().family() == "Source Sans Pro" assert QFont.Bold == activity_status_bar.font().weight() assert activity_status_bar.font().pixelSize() == 12 assert activity_status_bar.palette().color(QPalette.Base).name() == "#000000" assert activity_status_bar.palette().color(QPalette.Foreground).name() == "#d3d8ea" - error_status_bar = main_window.top_pane.error_status_bar + error_status_bar = main_window.bottom_pane.error_status_bar assert error_status_bar.vertical_bar.palette().color(QPalette.Background).name() == "#ff3366" assert error_status_bar.status_bar.font().family() == "Source Sans Pro" assert QFont.Normal == error_status_bar.status_bar.font().weight()