From 24ba914cc8951cab6f128a1489ac008802ec6e7b Mon Sep 17 00:00:00 2001 From: Alex Pyrgiotis Date: Tue, 25 Jul 2023 16:11:21 +0300 Subject: [PATCH] updater: Differentiate between "X" and "Cancel" We want to differentiate between the user clicking on "Cancel" and clicking on "X", since in the second case, we want to remind them again on the next run. --- dangerzone/gui/updater.py | 29 +++++++++++++++++++++++++---- tests/gui/test_updater.py | 11 ++++++----- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/dangerzone/gui/updater.py b/dangerzone/gui/updater.py index 0513e78..411fd59 100644 --- a/dangerzone/gui/updater.py +++ b/dangerzone/gui/updater.py @@ -43,6 +43,24 @@ about updates, check our UPDATE_CHECK_COOLDOWN_SECS = 60 * 60 * 12 # Check for updates at most every 12 hours. +class UpdateCheckPrompt(Alert): + """The prompt that asks the users if they want to enable update checks.""" + + x_pressed = False + + def closeEvent(self, event: QtCore.QEvent) -> None: + """Detect when a user has pressed "X" in the title bar. + + This function is called when a user clicks on "X" in the title bar. We want to + differentiate between the user clicking on "Cancel" and clicking on "X", since + in the second case, we want to remind them again on the next run. + + See: https://stackoverflow.com/questions/70851063/pyqt-differentiate-between-close-function-and-title-bar-close-x + """ + self.x_pressed = True + event.accept() + + class UpdateReport: """A report for an update check.""" @@ -98,17 +116,20 @@ class UpdaterThread(QtCore.QThread): def check(self, val: bool) -> None: self.dangerzone.settings.set("updater_check", val, autosave=True) - def prompt_for_checks(self) -> bool: + def prompt_for_checks(self) -> Optional[bool]: """Ask the user if they want to be informed about Dangerzone updates.""" log.debug("Prompting the user for update checks") # FIXME: Handle the case where a user clicks on "X", instead of explicitly # making a choice. We should probably ask them again on the next run. - check = Alert( + prompt = UpdateCheckPrompt( self.dangerzone, message=MSG_CONFIRM_UPDATE_CHECKS, ok_text="Yes", cancel_text="No", - ).launch() + ) + check = prompt.launch() + if not check and prompt.x_pressed: + return None return bool(check) def should_check_for_updates(self) -> bool: @@ -140,7 +161,7 @@ class UpdaterThread(QtCore.QThread): if self.check is None: log.debug("User has not been asked yet for update checks") self.check = self.prompt_for_checks() - return self.check + return bool(self.check) elif not self.check: log.debug("User has expressed that they don't want to check for updates") return False diff --git a/tests/gui/test_updater.py b/tests/gui/test_updater.py index 59b9c63..fa0acc2 100644 --- a/tests/gui/test_updater.py +++ b/tests/gui/test_updater.py @@ -156,18 +156,19 @@ def test_user_prompts( # # When Dangerzone runs for a second time, users can be prompted to enable update # checks. Depending on their answer, we should either enable or disable them. - alert_mock = mocker.MagicMock() - monkeypatch.setattr(updater_module, "Alert", alert_mock) + mocker.patch("dangerzone.gui.updater.UpdateCheckPrompt") + prompt_mock = updater_module.UpdateCheckPrompt + prompt_mock().x_pressed = False # Check disabling update checks. - alert_mock().launch.return_value = False + prompt_mock().launch.return_value = False # type: ignore [attr-defined] expected_settings["updater_check"] = False assert updater.should_check_for_updates() == False assert updater.dangerzone.settings.get_updater_settings() == expected_settings # Reset the "updater_check" field and check enabling update checks. updater.dangerzone.settings.set("updater_check", None) - alert_mock().launch.return_value = True + prompt_mock().launch.return_value = True # type: ignore [attr-defined] expected_settings["updater_check"] = True assert updater.should_check_for_updates() == True assert updater.dangerzone.settings.get_updater_settings() == expected_settings @@ -176,7 +177,7 @@ def test_user_prompts( # # From the third run onwards, users should never be prompted for enabling update # checks. - alert_mock.side_effect = RuntimeError("Should not be called") + prompt_mock().side_effect = RuntimeError("Should not be called") # type: ignore [attr-defined] for check in [True, False]: updater.dangerzone.settings.set("updater_check", check) assert updater.should_check_for_updates() == check