Skip to content

Commit

Permalink
Replace boolean gui.IsInMessageBox with function gui.message.isModalM…
Browse files Browse the repository at this point in the history
…essageBoxActive (#13376)

Relates to #13007, as the new design should be backwards compatible, we can implement the API changes as-is for 2022.1.
Follow up to the API proposed in #12984

Summary of the issue:
The API for gui.message had not yet been determined for 2022.1, as per #13007.
As the future API is intended to be backwards compatible, this PR commits to the API proposed in #12984.

Description of how this pull request fixes the issue:
gui.IsInMessageBox was a boolean, this has been replaced by a function gui.message.isModalMessageBoxActive, which tracks if a messageBox is open in a safer manner.

Changes logic gui/message.py to be safer with locking and handling errors.
  • Loading branch information
seanbudd authored Mar 3, 2022
1 parent 2d2a269 commit 9fbcbc0
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 28 deletions.
4 changes: 2 additions & 2 deletions source/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -319,11 +319,11 @@ def triggerNVDAExit(newNVDA: Optional[NewNVDAInstance] = None) -> bool:
instance information with `newNVDA`.
@return: True if this is the first call to trigger the exit, and the shutdown event was queued.
"""
from gui.message import isInMessageBox
from gui.message import isModalMessageBoxActive
import queueHandler
global _hasShutdownBeenTriggered
with _shuttingDownFlagLock:
safeToExit = not isInMessageBox()
safeToExit = not isModalMessageBoxActive()
if not safeToExit:
log.error("NVDA cannot exit safely, ensure open dialogs are closed")
return False
Expand Down
22 changes: 8 additions & 14 deletions source/gui/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,8 @@
import queueHandler
import core
from . import guiHelper
from buildVersion import version_year
from .message import (
isInMessageBox as _isInMessageBox,
isModalMessageBoxActive,
# messageBox is accessed through `gui.messageBox` as opposed to `gui.message.messageBox` throughout NVDA,
# be cautious when removing
messageBox,
Expand Down Expand Up @@ -61,11 +60,6 @@
### Globals
mainFrame = None

if version_year < 2022:
# Like other top level variables, this must be used as follows (#13011):
# import gui; doSomething(gui.isInMessageBox)
# NOT the following: from gui import isInMessageBox; doSomething(isInMessageBox)
isInMessageBox = False

class MainFrame(wx.Frame):

Expand Down Expand Up @@ -159,7 +153,7 @@ def onSaveConfigurationCommand(self,evt):
messageBox(_("Could not save configuration - probably read only file system"),_("Error"),wx.OK | wx.ICON_ERROR)

def _popupSettingsDialog(self, dialog, *args, **kwargs):
if _isInMessageBox():
if isModalMessageBoxActive():
return
self.prePopup()
try:
Expand Down Expand Up @@ -208,7 +202,7 @@ def evaluateUpdatePendingUpdateMenuItemCommand(self):
self.sysTrayIcon.menu.Insert(self.sysTrayIcon.installPendingUpdateMenuItemPos,self.sysTrayIcon.installPendingUpdateMenuItem)

def onExitCommand(self, evt):
if _isInMessageBox():
if isModalMessageBoxActive():
return
if config.conf["general"]["askToExit"]:
self.prePopup()
Expand Down Expand Up @@ -311,7 +305,7 @@ def onPythonConsoleCommand(self, evt):
pythonConsole.activate()

def onAddonsManagerCommand(self,evt):
if _isInMessageBox() or globalVars.appArgs.secure:
if isModalMessageBoxActive() or globalVars.appArgs.secure:
return
self.prePopup()
from .addonGui import AddonsDialog
Expand All @@ -327,7 +321,7 @@ def onReloadPluginsCommand(self, evt):
NVDAObject.clearDynamicClassCache()

def onCreatePortableCopyCommand(self,evt):
if _isInMessageBox():
if isModalMessageBoxActive():
return
self.prePopup()
import gui.installerGui
Expand All @@ -336,13 +330,13 @@ def onCreatePortableCopyCommand(self,evt):
self.postPopup()

def onInstallCommand(self, evt):
if _isInMessageBox():
if isModalMessageBoxActive():
return
from gui import installerGui
installerGui.showInstallGui()

def onRunCOMRegistrationFixesCommand(self, evt):
if _isInMessageBox():
if isModalMessageBoxActive():
return
if messageBox(
# Translators: A message to warn the user when starting the COM Registration Fixing tool
Expand Down Expand Up @@ -376,7 +370,7 @@ def onRunCOMRegistrationFixesCommand(self, evt):
)

def onConfigProfilesCommand(self, evt):
if _isInMessageBox():
if isModalMessageBoxActive():
return
self.prePopup()
from .configProfiles import ProfilesDialog
Expand Down
48 changes: 36 additions & 12 deletions source/gui/message.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
# -*- coding: UTF-8 -*-
# A part of NonVisual Desktop Access (NVDA)
# Copyright (C) 2006-2021 NV Access Limited, Peter Vágner, Aleksey Sadovoy, Mesar Hameed, Joseph Lee,
# Copyright (C) 2006-2022 NV Access Limited, Peter Vágner, Aleksey Sadovoy, Mesar Hameed, Joseph Lee,
# Thomas Stivers, Babbage B.V., Accessolutions, Julien Cochuyt
# This file is covered by the GNU General Public License.
# See the file COPYING for more details.

from buildVersion import version_year
import threading
from typing import Optional
import wx
Expand All @@ -14,8 +13,24 @@
_messageBoxCounter = 0


def isInMessageBox() -> bool:
return _messageBoxCounter != 0
def isModalMessageBoxActive() -> bool:
"""
`gui.message.messageBox` is a function which blocks the calling thread,
until a user responds to the modal dialog.
When some action (e.g. quitting NVDA) should be prevented due to any active modal message box,
even if unrelated, use `isModalMessageBoxActive` to check before triggering the action.
NVDA is in an uncertain state while waiting for an answer from a `gui.message.messageBox`.
It's possible for multiple message boxes to be open at a time.
This function can be used to check before opening subsequent `gui.message.messageBox` instances.
Because an answer is required to continue after a modal messageBox is opened,
some actions such as shutting down are prevented while NVDA is in a possibly uncertain state.
@return: True if a thread blocking modal response is still pending.
"""
with _messageBoxCounterLock:
return _messageBoxCounter != 0


def messageBox(
Expand All @@ -25,30 +40,39 @@ def messageBox(
parent: Optional[wx.Window] = None
) -> int:
"""Display a message dialog.
This should be used for all message dialogs
rather than using C{wx.MessageDialog} and C{wx.MessageBox} directly.
Avoid using C{wx.MessageDialog} and C{wx.MessageBox} directly.
@param message: The message text.
@param caption: The caption (title) of the dialog.
@param style: Same as for wx.MessageBox.
@param parent: The parent window.
@return: Same as for wx.MessageBox.
`gui.message.messageBox` is a function which blocks the calling thread,
until a user responds to the modal dialog.
This function should be used when an answer is required before proceeding.
Consider using a custom subclass of a wxDialog if an answer is not required
or a default answer can be provided.
It's possible for multiple message boxes to be open at a time.
Before opening a new messageBox, use `isModalMessageBoxActive`
to check if another messageBox modal response is still pending.
Because an answer is required to continue after a modal messageBox is opened,
some actions such as shutting down are prevented while NVDA is in a possibly uncertain state.
"""
from gui import mainFrame
import gui
global _messageBoxCounter
with _messageBoxCounterLock:
_messageBoxCounter += 1
if version_year < 2022:
gui.isInMessageBox = True

try:
if not parent:
mainFrame.prePopup()
res = wx.MessageBox(message, caption, style, parent or mainFrame)
finally:
if not parent:
mainFrame.postPopup()
finally:
with _messageBoxCounterLock:
_messageBoxCounter -= 1
if version_year < 2022:
gui.isInMessageBox = isInMessageBox()

return res
1 change: 1 addition & 0 deletions user_docs/en/changes.t2t
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ This can dramatically decrease build times on multi core systems. (#13226, #1337
- ``cachedAutomationId`` can be used if obtained directly from the element.
-
- ``NVDAObjects.window.scintilla.CharacterRangeStruct`` has moved to ``NVDAObjects.window.scintilla.Scintilla.CharacterRangeStruct``. (#13364)
- Boolean ``gui.isInMessageBox`` is removed, please use the function ``gui.message.isModalMessageBoxActive`` instead. (#12984, #13376)
- ``controlTypes`` has been split up into various submodules. (#12510)
- ``ROLE_*`` and ``STATE_*`` have been replaced with ``Role.*`` and ``State.*``.
- ``roleLabels``, ``stateLabels`` and ``negativeStateLabels`` has been removed.
Expand Down

0 comments on commit 9fbcbc0

Please sign in to comment.