Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Message Dialog API take 2 #17582

Merged
merged 221 commits into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
221 commits
Select commit Hold shift + click to select a range
dfdd299
Working refactored message dialog based on extant message dialog and …
SaschaCowley Sep 18, 2024
38c99cb
Improve positioning
SaschaCowley Sep 19, 2024
07ba293
Explicitly focus default item
SaschaCowley Sep 19, 2024
a479a4f
Improved types and type hints
SaschaCowley Sep 19, 2024
3326a98
Switched to setLabelText to avoid mnemonic issue
SaschaCowley Sep 19, 2024
1a8f82f
Switched to icon bundles
SaschaCowley Sep 24, 2024
99dd605
Renamed private methods to be dunder
SaschaCowley Sep 24, 2024
2a3df49
Some manual test cases
SaschaCowley Sep 24, 2024
947f669
Added check for default item before focusing
SaschaCowley Sep 24, 2024
6bd8c8a
Minor changes to make temp code work better
SaschaCowley Sep 24, 2024
2e255cb
Implementation of addButton, addOkButton and addCancelButton
SaschaCowley Sep 25, 2024
ce826f2
Return self for chaining, and add a option to set as default
SaschaCowley Sep 25, 2024
9a7dbf8
Added close functionality
SaschaCowley Sep 25, 2024
768e052
Fixed layout issues and updated test case
SaschaCowley Sep 25, 2024
7e6754f
Added post popup call
SaschaCowley Sep 25, 2024
601a193
Added infrastructure for updates to API not yet implemented
SaschaCowley Sep 26, 2024
64801d9
Merge branch 'master' into messageDialogApi
SaschaCowley Sep 26, 2024
a44063d
Swiutch to using partials for callback calling
SaschaCowley Sep 26, 2024
3e5b03c
Added translator comments and some docstrings
SaschaCowley Oct 2, 2024
e7e1d29
Added type checking to `__call_callback`
SaschaCowley Oct 3, 2024
08360fd
Made `addButton` a `singledispatchmethod` that will add a `MessageDia…
SaschaCowley Oct 3, 2024
917d8e4
Refactored `addOkButton` and `addCancelButton` to be `partialmethod`s…
SaschaCowley Oct 3, 2024
273d771
Added multi button add method
SaschaCowley Oct 4, 2024
8f1bcba
Documentation improvements
SaschaCowley Oct 4, 2024
5e9d4c5
Added deque to track open MessageDialog instances
SaschaCowley Oct 7, 2024
f8cde12
Refactored to use a registry oif callbacks
SaschaCowley Oct 7, 2024
ac2be85
Added adders for oother default button types
SaschaCowley Oct 7, 2024
bcc49f9
Added custom button IDs
SaschaCowley Oct 7, 2024
1a95e06
Various code clean-ups
SaschaCowley Oct 7, 2024
5ec943e
Partially working modal dialogs
SaschaCowley Oct 7, 2024
dbca144
Better handling of modal dialogs (still not woroking properly as bloc…
SaschaCowley Oct 8, 2024
c0d0f35
Fixed problem with blocking instances exist
SaschaCowley Oct 8, 2024
2140a45
WIP better performance tracing and layout encapsulation
SaschaCowley Oct 9, 2024
0beba17
Reorganised MessageDialog code
SaschaCowley Oct 10, 2024
648ad0a
Fixed return codes not working
SaschaCowley Oct 10, 2024
2f82b4b
Explicitly hide dialog when closing
SaschaCowley Oct 10, 2024
3053514
Added escape code enum to support ID_NONE and ID_ANY
SaschaCowley Oct 10, 2024
b0b2449
Changed MessageDialogButton.default to MessageDialogButton.default_fo…
SaschaCowley Oct 10, 2024
898c24e
Corrected variable names
SaschaCowley Oct 10, 2024
92bc0ff
Removed staticmethods to classmethods
SaschaCowley Oct 10, 2024
0a66a76
Made isBlocking a property
SaschaCowley Oct 18, 2024
1ee10c3
Improved type annotations
SaschaCowley Oct 18, 2024
c302f72
Documentation improvements
SaschaCowley Oct 18, 2024
0010195
Made Show and ShowModal work in line with wx.Dialog expectations
SaschaCowley Oct 18, 2024
23172ad
Added help id argument
SaschaCowley Oct 18, 2024
bbe7c33
Added exception for trying to show a MessageDialog without buttons
SaschaCowley Oct 18, 2024
81ba8cb
Renamed DefaultMessageDialogButtons to DefaultMessageDialogButton
SaschaCowley Oct 18, 2024
a79d98d
Added an enumeration of common button combinations, and a buttons par…
SaschaCowley Oct 18, 2024
58edca2
Used MessageDialog for about dialog
SaschaCowley Oct 18, 2024
c9c4eff
Added deprecation warning to gui.message.messageBox
SaschaCowley Oct 28, 2024
e740ac7
Re-enabled system test "Quits from keyboard with about dialog open"
SaschaCowley Oct 28, 2024
335f58f
Updated block action to bring blocking dialogs to foreground
SaschaCowley Oct 28, 2024
db03d56
Default action forces close
SaschaCowley Oct 28, 2024
6766cf5
Added a docstring
SaschaCowley Oct 29, 2024
d0ff5cf
Added a draft class-level docstring
SaschaCowley Oct 29, 2024
cb247ab
Respect CloseEvent.CanVeto
SaschaCowley Oct 29, 2024
0f4a4f5
Initial message dialog unit tests
SaschaCowley Oct 30, 2024
1d02fff
Refactored to use a common base class
SaschaCowley Oct 30, 2024
7987c30
Removed default button assignment (reimplement later)
SaschaCowley Oct 30, 2024
eca4e67
Tests for adding standard buttons
SaschaCowley Oct 30, 2024
9fdd603
Added support for adding DefaultMessageDialogButtons with MessageDial…
SaschaCowley Oct 31, 2024
b5a928c
Added methods to easily add default buttons and unit tests for same
SaschaCowley Oct 31, 2024
214e62b
Add hasDefaultAction property
SaschaCowley Nov 1, 2024
8c11e0b
Add docstrings
SaschaCowley Nov 1, 2024
2e836ae
Improved implementation of default action getters
SaschaCowley Nov 1, 2024
561e9d0
Slightly refactored tests and improved docstrings for some tests
SaschaCowley Nov 1, 2024
86fc674
Slight refacter to closing logic
SaschaCowley Nov 1, 2024
725ef74
Re-implemented get default action and tests for same
SaschaCowley Nov 3, 2024
48ec32b
Made _getDefaultActionOrFallback comply with the return type opf _get…
SaschaCowley Nov 3, 2024
4114a0a
Added additional setters and tests
SaschaCowley Nov 4, 2024
b4ff1e1
Call default callback when closing programatically
SaschaCowley Nov 4, 2024
a1faa51
Added docstrings to tests
SaschaCowley Nov 4, 2024
d751804
Made fallback default action more robust
SaschaCowley Nov 4, 2024
abb5a6d
Renamed several double-underscored variables and methods to be single…
SaschaCowley Nov 4, 2024
90117c3
Added tests and docstrings
SaschaCowley Nov 4, 2024
734703f
Added lots of docstrings.
SaschaCowley Nov 4, 2024
a4b708a
Made a bunch of class names more usable
SaschaCowley Nov 4, 2024
540b1bc
Fixed some type info
SaschaCowley Nov 4, 2024
ed30a0c
Added tests for _flatten_buttons
SaschaCowley Nov 5, 2024
7577e91
Made the Show method comply with that of wx.Dialog
SaschaCowley Nov 5, 2024
e7b5f60
Documentation improvements
SaschaCowley Nov 6, 2024
e0917b8
Restored behaviour of having an OK button as default
SaschaCowley Nov 6, 2024
e422e26
Fixed label not being applied to buttons.
SaschaCowley Nov 6, 2024
403fed6
Made id and button positional-only
SaschaCowley Nov 6, 2024
dc7b459
Rename several button properties to bring them into line with NVDA co…
SaschaCowley Nov 11, 2024
bd63423
Parameterized tests for the default button adders to make code more DRY
SaschaCowley Nov 11, 2024
3efdb80
Test that buttons are unique when adding
SaschaCowley Nov 13, 2024
67d5970
Changed addButtons to accept an Collection of Buttons, rather than an…
SaschaCowley Nov 13, 2024
1401640
Remove _flattenButtons
SaschaCowley Nov 13, 2024
3c59af3
Fixed some types
SaschaCowley Nov 13, 2024
b095d2e
Tightened some logic and made the close box enabled only when there i…
SaschaCowley Nov 13, 2024
904befd
Fixed test dialog
SaschaCowley Nov 13, 2024
59ea918
Formatted
SaschaCowley Nov 13, 2024
49c6a2d
Reimplemented gui.message.messageBox using MessageDialog
SaschaCowley Nov 14, 2024
d567f1f
Re-implemented the message box shim
SaschaCowley Nov 15, 2024
fca7eb8
Refactored and documented.
SaschaCowley Nov 15, 2024
0bcb7dc
Added a helper function for calling wx functions on the main thread a…
SaschaCowley Nov 18, 2024
5174826
Used new wxCallOnMain function foor message box shim
SaschaCowley Nov 18, 2024
c24b49e
Tidied up message box shim
SaschaCowley Nov 18, 2024
209b653
Added new function to changes
SaschaCowley Nov 18, 2024
b0e686d
Only allow instantiating message dialogs from the GUI thread
SaschaCowley Nov 18, 2024
9757bb2
Fixed implementation of isBlocking and FocusBlockingInstances
SaschaCowley Nov 18, 2024
2c16430
Added helper methods for alert, confirm and query dialogs
SaschaCowley Nov 18, 2024
594c943
Added some methods to be implemented
SaschaCowley Nov 18, 2024
9072f3a
Added more main thread checks
SaschaCowley Nov 20, 2024
f3331c3
Started implementation of message.MessageDialog -> messageDialog.Mess…
SaschaCowley Nov 20, 2024
e266bc9
Fixed duplicate superclass of gui.nvdaControls._ContinueCancelDialog
SaschaCowley Nov 20, 2024
5655da1
Refactored show checks into their own private method
SaschaCowley Nov 20, 2024
27352c3
Old message dialog shimmed to new one and working with screen curtain…
SaschaCowley Nov 20, 2024
e65e74b
Refactor checks to make them easier to override in subclasses.
SaschaCowley Nov 21, 2024
e72cf5d
Shim for gui.nvdaControls.MessageDialog seems to work fine.
SaschaCowley Nov 21, 2024
5cb75b9
Add sentinel value to allow discrimination of `None` and "not provided".
SaschaCowley Nov 29, 2024
6cabca8
Allow overriding the implicit return code of the action ID by providi…
SaschaCowley Nov 29, 2024
d99f5a4
Update shims of gui.nvdaControls.MessageDialog and gui.nvdaControls._…
SaschaCowley Nov 29, 2024
c4dc3f9
Update changelog
SaschaCowley Nov 29, 2024
1278d16
Added means of changing button labels.
SaschaCowley Nov 29, 2024
c55c6e1
Added the ability to set the message text independent of the __init__
SaschaCowley Nov 29, 2024
cc5274a
Improve logic of alert, confirm and ask classmethods.
SaschaCowley Dec 2, 2024
17fd510
Slight reorganisation in some methods.
SaschaCowley Dec 2, 2024
3e9850a
Code documentation improvements.
SaschaCowley Dec 2, 2024
9d80459
Docstring for MessageDialog._commands
SaschaCowley Dec 3, 2024
ff61f9d
Improved logic for whether dialogs are blocking
SaschaCowley Dec 3, 2024
86c472a
Added callback field to gui.blockAction._Context, and refactored the …
SaschaCowley Dec 3, 2024
01284de
Code style and documentation for gui.guiHelper.wxCallOnMain.
SaschaCowley Dec 3, 2024
1f0cfba
Code clean up and documentation improvements to gui.message.messageBox.
SaschaCowley Dec 3, 2024
e9942ce
Documentation improvements to gui.nvdaControls.MessageDialog
SaschaCowley Dec 3, 2024
129fcea
Removed old gui.nvdaControls.MessageDialog code.
SaschaCowley Dec 3, 2024
fc9e35b
Added commit references to changes
SaschaCowley Dec 3, 2024
d2eef13
Fixed type hints for MethodCall
SaschaCowley Dec 4, 2024
b55c0e5
Changed getDialogState to return a dictionary of button IDs and their…
SaschaCowley Dec 4, 2024
9065524
Fixed return from getDialogState
SaschaCowley Dec 4, 2024
0782453
Added tests for setting button labels.
SaschaCowley Dec 4, 2024
c5f55b3
Parameterised tests of setIcon and playSound to cover all dialog types.
SaschaCowley Dec 4, 2024
b947d20
Fixed test name
SaschaCowley Dec 4, 2024
acd1240
Added test for adding buttons with custom overrides.
SaschaCowley Dec 5, 2024
adc2d23
Removed use of deprecated log.warn
SaschaCowley Dec 5, 2024
2cfa0f8
Added test for adding buttons with non-unique IDs
SaschaCowley Dec 5, 2024
8816470
Fixed use of FindWindowById to FindWindow(id)
SaschaCowley Dec 5, 2024
371848a
Added tests for setting default focus.
SaschaCowley Dec 5, 2024
7a28c4b
Removed unnecessary error case from _getFallbackAction
SaschaCowley Dec 5, 2024
1d033db
Fixed logic for when the default focus is not in command registry
SaschaCowley Dec 5, 2024
a4a4e7c
Added further fallback action tests
SaschaCowley Dec 5, 2024
acad2ff
Added more tests for setting button labels
SaschaCowley Dec 5, 2024
b5a3895
Refactored MdTestBase into WxTestBase -> MdTestBase
SaschaCowley Dec 5, 2024
614e649
Added tests for threading
SaschaCowley Dec 5, 2024
b1a3457
Fixed test case name
SaschaCowley Dec 5, 2024
020a78b
Added unittests for showing and hiding
SaschaCowley Dec 5, 2024
7559b3f
Improved modal show tests.
SaschaCowley Dec 5, 2024
428a6ed
Added tests for showEvent
SaschaCowley Dec 5, 2024
f4ed2d1
Fixed implementation of _getFallbackAction, _getFallbackActionOrFallb…
SaschaCowley Dec 5, 2024
30b8c64
Added tests for blocking dialogs.
SaschaCowley Dec 6, 2024
6f2d760
Fixed issue when force-closing non-modal dialog
SaschaCowley Dec 6, 2024
e5054ab
Renamed _execute_command to _executeCommand
SaschaCowley Dec 6, 2024
ca5e0ad
Added tests for _onCloseEvent
SaschaCowley Dec 6, 2024
d8073b4
Added test for closeInstances
SaschaCowley Dec 6, 2024
3230860
Added tests for isBlocking
SaschaCowley Dec 6, 2024
05c7540
Renamed hasDefaultAction to hasFallback to match rename of defaultAct…
SaschaCowley Dec 6, 2024
8b22b04
Added tests for executeCommand
SaschaCowley Dec 6, 2024
c419a58
Added guidance on dialog types
SaschaCowley Dec 6, 2024
c26dfb5
Renamed setDefaultAction to setFallbackAction
SaschaCowley Dec 6, 2024
3b3be1b
Doocstring improvements
SaschaCowley Dec 6, 2024
679b2a7
Renamed EscapeCode members to be more self documenting
SaschaCowley Dec 6, 2024
57ca154
Renamed _realize_layout to _realizeLayout in line with NV Access style
SaschaCowley Dec 6, 2024
d00e5ea
Added a bunch of missing docstrings
SaschaCowley Dec 6, 2024
b38630a
Removed some commented out code
SaschaCowley Dec 6, 2024
aa207b5
Initial docs
SaschaCowley Dec 6, 2024
ec43446
Added docstring to __init__
SaschaCowley Dec 6, 2024
6b5e084
Added documentation of convenience methods to dev docs
SaschaCowley Dec 6, 2024
ead1ec5
Moved DialogType to gui.message
SaschaCowley Dec 6, 2024
0cf5f15
Moved EscapeCode to gui.message
SaschaCowley Dec 6, 2024
4f86bfd
Moved DialogType to gui.message
SaschaCowley Dec 6, 2024
6bb8a4c
Moved over the rest of gui.messageDialog to gui.message
SaschaCowley Dec 6, 2024
9ebe8cc
Updated copyright years
SaschaCowley Dec 6, 2024
8df3553
Improvements to tests
SaschaCowley Dec 6, 2024
e9b95d0
Renamed tests to be more in line with NVDA style
SaschaCowley Dec 6, 2024
a850585
Removed testing items from NVDA menu.
SaschaCowley Dec 6, 2024
a4b82e6
Updated dev docs with information on importing
SaschaCowley Dec 6, 2024
2ce6839
Updated changes
SaschaCowley Dec 6, 2024
18f8fc9
Deleted old messageDialog.py file
SaschaCowley Dec 6, 2024
23a6114
Merge branch 'master' into messageDialogApi
SaschaCowley Dec 6, 2024
be3f223
Pre-commit auto-fix
pre-commit-ci[bot] Dec 6, 2024
1bb02f7
Made delay before block message a constant
SaschaCowley Dec 10, 2024
81be523
Fixed misspelling
SaschaCowley Dec 10, 2024
581dc33
Added explicit import to first example in developer guide for clarity
SaschaCowley Dec 10, 2024
3dc1206
Made thread safety warning more prominant
SaschaCowley Dec 10, 2024
e256e4f
Added note on custom button IDs
SaschaCowley Dec 10, 2024
7db3eb1
Fixed outdated docstring
SaschaCowley Dec 10, 2024
c29ecf6
Added error checking to _onCloseEvent
SaschaCowley Dec 10, 2024
20f0e59
Renamed event handlers to be more consistant
SaschaCowley Dec 11, 2024
6c68ce6
Update requirements.txt
SaschaCowley Dec 11, 2024
c296eb9
Update source/gui/blockAction.py
SaschaCowley Dec 11, 2024
39fbd5b
Rewrote wxCallOnMain to use nonlocal rather than helper class
SaschaCowley Dec 11, 2024
c5d775e
Update source/gui/guiHelper.py
SaschaCowley Dec 11, 2024
a59912a
Update source/gui/guiHelper.py
SaschaCowley Dec 11, 2024
85cbef9
Update user_docs/en/changes.md
SaschaCowley Dec 11, 2024
21614a9
Made dialog destruction and deletion more robust
SaschaCowley Dec 11, 2024
8ae47bc
Fixed incorrect call to warn
SaschaCowley Dec 11, 2024
81cc995
Made runScriptModal safer, and marked it for removal
SaschaCowley Dec 11, 2024
448630b
Merge branch 'i13007' of https://github.com/nvaccess/nvda into messag…
SaschaCowley Dec 11, 2024
41c01f5
Added type hints to _onActivateEvent and _onShowEvent for the screen …
SaschaCowley Dec 12, 2024
8990d9c
Apply suggestions from code review
SaschaCowley Dec 12, 2024
08bbbd1
Changed some methods to use lower camel case
SaschaCowley Dec 12, 2024
db53c83
Improved logging
SaschaCowley Dec 12, 2024
ca09d42
Apply suggestions from code review
SaschaCowley Dec 12, 2024
da2fe29
Removed footnotes from the developer guide
SaschaCowley Dec 12, 2024
451f370
Removed __del__ that was failing
SaschaCowley Dec 12, 2024
fcafc8a
Corrects to dev guide
SaschaCowley Dec 13, 2024
0c7a6c7
Corrections to dev guide
SaschaCowley Dec 13, 2024
65ced3c
Merge branch 'i13007' of https://github.com/nvaccess/nvda into messag…
SaschaCowley Dec 13, 2024
8b02b72
Apply suggestions from code review
SaschaCowley Dec 13, 2024
0dce0e9
Made several parameterized argument lists namedtuples for readability
SaschaCowley Dec 13, 2024
0c99151
Merge branch 'master' into messageDialogApi
SaschaCowley Dec 13, 2024
f52a07e
Fixed up admonissions
SaschaCowley Dec 18, 2024
f437723
Added notes on thread safety
SaschaCowley Dec 18, 2024
ba88884
Restore MessageDialog inheriting from ContextHelpMixin
SaschaCowley Dec 20, 2024
374dc7e
Merge branch 'master' into messageDialogApi2
SaschaCowley Jan 6, 2025
3893564
Slightly refactor `UpdateAskInstallDialog` not to take any action on …
SaschaCowley Jan 6, 2025
28931c2
Documentation
SaschaCowley Jan 6, 2025
20a56db
Updated other instances to use new call style
SaschaCowley Jan 6, 2025
ae893e4
Update changes
SaschaCowley Jan 6, 2025
0a25805
Update source/updateCheck.py
SaschaCowley Jan 6, 2025
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
230 changes: 230 additions & 0 deletions projectDocs/dev/developerGuide/developerGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -1445,3 +1445,233 @@ Please see the `EventExtensionPoints` class documentation for more information,
|`Action` |`post_reviewMove` |the position of the review cursor has changed.|
|`Action` |`post_mouseMove` |the mouse has moved.|
|`Action` |`post_coreCycle` |the end of each core cycle has been reached.|

## Communicating with the user

### The message dialog API

The message dialog API provides a flexible way of presenting interactive messages to the user.
The messages are highly customisable, with options to change icons and sounds, button labels, return values, and close behaviour, as well as to attach your own callbacks.

All classes that make up the message dialog API are importable from `gui.message`.
While you are unlikely to need all of them, they are enumerated below:

* `ReturnCode`: Possible return codes from modal `MessageDialog`s.
* `EscapeCode`: Escape behaviour of `MessageDialog`s.
* `DialogType`: Types of dialogs (sets the dialog's sound and icon).
* `Button`: Button configuration data structure.
* `DefaultButton`: Enumeration of pre-configured buttons.
* `DefaultButtonSet`: Enumeration of common combinations of buttons.
* `MessageDialog`: The actual dialog class.

In many simple cases, you will be able to achieve what you need by simply creating a message dialog and calling `Show` or `ShowModal`. For example:

```py
from gui.message import MessageDialog
from gui import mainFrame

MessageDialog(
mainFrame,
_("Hello world!"),
).Show()
```

This will show a non-modal (that is, non-blocking) dialog with the text "Hello world!" and an OK button.

If you want the dialog to be modal (that is, to block the user from performing other actions in NVDA until they have responded to it), you can call `ShowModal` instead.

With modal dialogs, the easiest way to respond to user input is via the return code.

```py
from gui.message import DefaultButtonSet, ReturnCode

saveDialog = MessageDialog(
mainFrame,
_("Would you like to save your changes before exiting?"),
_("Save changes?"),
buttons=DefaultButtonSet.SAVE_NO_CANCEL
)

match saveDialog.ShowModal():
case ReturnCode.SAVE:
... # Save the changes and close
case ReturnCode.NO:
... # Discard changes and close
case ReturnCode.CANCEL:
... # Do not close
```

For non-modal dialogs, the easiest way to respond to the user pressing a button is via callback methods.

```py
def readChangelog():
... # Do something

def downloadUpdate():
... # Do something

def remindLater():
... # Do something

updateDialog = MessageDialog(
mainFrame,
"An update is available. "
"Would you like to download it now?",
"Update",
buttons=None,
).addYesButton(
callback=downloadUpdate
).addNoButton(
label=_("&Remind me later"),
fallbackAction=True,
callback=remindLater
).addHelpButton(
label=_("What's &new"),
callback=readChangelog
)

updateDialog.Show()
```

You can set many of the parameters to `addButton` later, too:

* The default focus can be set by calling `setDefaultFocus` on your message dialog instance, and passing it the ID of the button to make the default focus.
* The fallback action can be set later by calling `setFallbackAction` or `SetEscapeId` with the ID of the button which performs the fallback action.
* The button's label can be changed by calling `setButtonLabel` with the ID of the button and the new label.

#### Fallback actions

The fallback action is the action performed when the dialog is closed without the user pressing one of the buttons you added to the dialog.
This can happen for several reasons:

* The user pressed `esc` or `alt+f4` to close the dialog.
* The user used the title bar close button or system menu close item to close the dialog.
* The user closed the dialog from the Task View, Taskbar or App Switcher.
* The user is quitting NVDA.
* Some other part of NVDA or an add-on has asked the dialog to close.

By default, the fallback action is set to `EscapeCode.CANCEL_OR_AFFIRMATIVE`.
This means that the fallback action will be the cancel button if there is one, the button whose ID is `dialog.GetAffirmativeId()` (`ReturnCode.OK`, by default), or `None` if no button with either ID exists in the dialog.
You can use `dialog.SetAffirmativeId(id)` to change the ID of the button used secondarily to Cancel, if you like.
The fallback action can also be set to `EscapeCode.NO_FALLBACK` to disable closing the dialog like this entirely.
If it is set to any other value, the value must be the id of a button to use as the default action.

In some cases, the dialog may be forced to close.
If the dialog is shown modally, a calculated fallback action will be used if the fallback action is `EscapeCode.NO_FALLBACK` or not found.
The order of precedence for calculating the fallback when a dialog is forced to close is as follows:

1. The developer-set fallback action.
2. The developer-set default focus.
3. The first button added to the dialog that closes the dialog.
4. The first button added to the dialog, regardless of whether it closes the dialog.
5. A dummy action that does nothing but close the dialog.
In this case, and only this case, the return code from showing the dialog modally will be `EscapeCode.NO_FALLBACK`.

#### A note on threading

**IMPORTANT:** Most `MessageDialog` methods are **not** thread safe.
Calling these methods from non-GUI threads can cause crashes or unpredictable behavior.

When calling non thread safe methods on `MessageDialog` or its instances, be sure to do so on the GUI thread.
To do this with wxPython, you can use `wx.CallAfter` or `wx.CallLater`.
As these operations schedule the passed callable to occur on the GUI thread, they will return immediately, and will not return the return value of the passed callable.
If you want to wait until the callable has completed, or care about its return value, consider using `gui.guiHelper.wxCallOnMain`.

The `wxCallOnMain` function executes the callable you pass to it, along with any positional and keyword arguments, on the GUI thread.
It blocks the calling thread until the passed callable returns or raises an exception, at which point it returns the returned value, or re-raises the raised exception.

```py
# To call
someFunction(arg1, arg2, kw1=value1, kw2=value2)
# on the GUI thread:
wxCallOnMain(someFunction, arg1, arg2, kw=value1, kw2=value2)
```

In fact, you cannot create, initialise, or show (modally or non-modally) `MessageDialog`s from any thread other than the GUI thread.

#### Buttons

You can add buttons in a number of ways:

* By passing a `Collection` of `Button`s to the `buttons` keyword-only parameter to `MessageDialog` when initialising.
* By calling `addButton` on a `MessageDialog` instance, either with a `Button` instance, or with simple parameters.
* When calling `addButton` with a `Button` instance, you can override all of its parameters except `id` by providing their values as keyword arguments.
* When calling `addButton` with simple parameters, the parameters it accepts are the same as those of `Button`.
* In both cases, `id` or `button` is the first argument, and is positional only.
* By calling `addButtons` with a `Collection` of `Button`s.
* By calling any of the add button helpers.

Regardless of how you add them, you cannot add multiple buttons with the same ID to the same `MessageDialog`.

A `Button` is an immutable data structure containing all of the information needed to add a button to a `MessageDialog`.
Its fields are as follows:

| Field | Type | Default | Explanation |
|---|---|---|---|
| `id` | `ReturnCode` | No default | The ID used to refer to the button. |
| `label` | `str` | No default | The text label to display on the button. Prefix accelerator keys with an ampersand (&). |
| `callback` | `Callable` or `None` | `None` | The function to call when the button is clicked. This is most useful for non-modal dialogs. |
| `defaultFocus` | `bool` | `False` | Whether to explicitly set the button as the default focus. (1) |
| `fallbackAction` | `bool` | `False` | Whether the button should be the fallback action, which is called when the user presses `esc`, uses the system menu or title bar close buttons, or the dialog is asked to close programmatically. (2) |
| `closesDialog` | `bool` | `True` | Whether the button should close the dialog when pressed. (3) |
| `returnCode` | `ReturnCode` or `None` | `None` | Value to return when a modal dialog is closed. If `None`, the button's ID will be used. |

1. Setting `defaultFocus` only overrides the default focus:

* If no buttons have this property, the first button will be the default focus.
* If multiple buttons have this property, the last one will be the default focus.

2. `fallbackAction` only sets whether to override the fallback action:

* This button will still be the fallback action if the dialog's fallback action is set to `EscapeCode.CANCEL_OR_AFFIRMATIVE` (the default) and its ID is `ReturnCode.CANCEL` (or whatever the value of `GetAffirmativeId()` is (`ReturnCode.OK`, by default), if there is no button with `id=ReturnCode.CANCEL`), even if it is added with `fallbackAction=False`.
To set a dialog to have no fallback action, use `setFallbackAction(EscapeCode.NO_FALLBACK)`.
* If multiple buttons have this property, the last one will be the fallback action.

3. Buttons with `fallbackAction=True` and `closesDialog=False` are not supported:

* When adding a button with `fallbackAction=True` and `closesDialog=False`, `closesDialog` will be set to `True`.
* If you attempt to call `setFallbackAction` with the ID of a button that does not close the dialog, `ValueError` will be raised.

A number of pre-configured buttons are available for you to use from the `DefaultButton` enumeration, complete with pre-translated labels.
None of these buttons will explicitly set themselves as the fallback action.
You can also add any of these buttons to an existing `MessageDialog` instance with its add button helper, which also allows you to override all but the `id` parameter.
The following default buttons are available:

| Button | Label | ID/return code | Closes dialog | Add button helper |
|---|---|---|---|---|
| `APPLY` | &Apply | `ReturnCode.APPLY` | No | `addApplyButton` |
| `CANCEL` | Cancel | `ReturnCode.CANCEL` | Yes | `addCancelButton` |
| `CLOSE` | Close | `ReturnCode.CLOSE` | Yes | `addCloseButton` |
| `HELP` | Help | `ReturnCode.HELP` | No | `addHelpButton` |
| `NO` | &No | `ReturnCode.NO` | Yes | `addNoButton` |
| `OK` | OK | `ReturnCode.OK` | Yes | `addOkButton` |
| `SAVE` | &Save | `ReturnCode.SAVE` | Yes | `addSaveButton` |
| `YES` | &Yes | `ReturnCode.YES` | Yes | `addYesButton` |

As you usually want more than one button on a dialog, there are also a number of pre-defined sets of buttons available as members of the `DefaultButtonSet` enumeration.
All of them comprise members of `DefaultButton`.
You can also add any of these default button sets to an existing `MessageDialog` with one of its add buttons helpers.
The following default button sets are available:

| Button set | Contains | Add button set helper | Notes |
|---|---|---|---|
| `OK_CANCEL` | `DefaultButton.OK` and `DefaultButton.Cancel` | `addOkCancelButtons` | |
| `YES_NO` | `DefaultButton.YES` and `DefaultButton.NO` | `addYesNoButtons` | You must set a fallback action if you want the user to be able to press escape to close a dialog with only these buttons. |
| `YES_NO_CANCEL` | `DefaultButton.YES`, `DefaultButton.NO` and `DefaultButton.CANCEL` | `addYesNoCancelButtons` | |
| `SAVE_NO_CANCEL` | `DefaultButton.SAVE`, `DefaultButton.NO`, `DefaultButton.CANCEL` | `addSaveNoCancelButtons` | The label of the no button is overridden to be "Do&n't save". |

If none of the standard `ReturnCode` values are suitable for your button, you may also use `ReturnCode.CUSTOM_1` through `ReturnCode.CUSTOM_5`, which will not conflict with any built-in identifiers.

#### Convenience methods

The `MessageDialog` class also provides a number of convenience methods for showing common types of modal dialogs.
Each of them requires a message string, and optionally a title string and parent window.
They all also support overriding the labels on their buttons via keyword arguments.
They are all thread safe.
The following convenience class methods are provided (keyword arguments for overriding button labels indicated in parentheses):

| Method | Buttons | Return values |
|---|---|---|
| `alert` | OK (`okLabel`) | `None` |
| `confirm` | OK (`okLabel`) and Cancel (`cancelLabel`) | `ReturnCode.OK` or `ReturnCode.Cancel` |
| `ask` | Yes (`yesLabel`), No (`noLabel`) and Cancel (`cancelLabel`) | `ReturnCode.YES`, `ReturnCode.NO` or `ReturnCode.CANCEL` |
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ nuitka==2.5.4

# Creating XML unit test reports
unittest-xml-reporting==3.2.0
# Feed parameters to tests neatly
parameterized==0.9.0

# Building user documentation
Markdown==3.7
Expand Down
6 changes: 4 additions & 2 deletions source/documentationUtils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- coding: UTF-8 -*-
# A part of NonVisual Desktop Access (NVDA)
# Copyright (C) 2006-2023 NV Access Limited, Łukasz Golonka
# Copyright (C) 2006-2024 NV Access Limited, Łukasz Golonka
# This file may be used under the terms of the GNU General Public License, version 2 or later.
# For more details see: https://www.gnu.org/licenses/gpl-2.0.html

Expand All @@ -13,7 +13,6 @@
from logHandler import log
import ui
import queueHandler
from gui.message import messageBox
import wx


Expand Down Expand Up @@ -65,6 +64,9 @@ def reportNoDocumentation(fileName: str, useMsgBox: bool = False) -> None:
f"Documentation not found ({fileName}): possible cause - running from source without building user docs.",
)
if useMsgBox:
# Import late to avoid circular impoort.
from gui.message import messageBox

messageBox(
noDocMessage,
# Translators: the title of an error message dialog
Expand Down
31 changes: 19 additions & 12 deletions source/gui/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
# This file is covered by the GNU General Public License.
# See the file COPYING for more details.

from collections.abc import Callable
import os
import ctypes
import warnings
import wx
import wx.adv

Expand All @@ -30,6 +32,8 @@
# messageBox is accessed through `gui.messageBox` as opposed to `gui.message.messageBox` throughout NVDA,
# be cautious when removing
messageBox,
MessageDialog,
displayDialogAsModal,
)
from . import blockAction
from .speechDict import (
Expand Down Expand Up @@ -285,7 +289,7 @@ def onExecuteUpdateCommand(self, evt):
apiVersion=apiVersion,
backCompatTo=backCompatToAPIVersion,
)
runScriptModalDialog(confirmUpdateDialog)
runScriptModalDialog(confirmUpdateDialog, confirmUpdateDialog.callback)
else:
updateCheck.executePendingUpdate()

Expand Down Expand Up @@ -367,7 +371,7 @@ def onInputGesturesCommand(self, evt):

def onAboutCommand(self, evt):
# Translators: The title of the dialog to show about info for NVDA.
messageBox(versionInfo.aboutMessage, _("About NVDA"), wx.OK)
MessageDialog(None, versionInfo.aboutMessage, _("About NVDA")).Show()

@blockAction.when(blockAction.Context.SECURE_MODE)
def onCheckForUpdateCommand(self, evt):
Expand Down Expand Up @@ -878,21 +882,24 @@ def showGui():
wx.CallAfter(mainFrame.showGui)


def runScriptModalDialog(dialog, callback=None):
def runScriptModalDialog(dialog: wx.Dialog, callback: Callable[[int], Any] | None = None):
"""Run a modal dialog from a script.
This will not block the caller,
but will instead call C{callback} (if provided) with the result from the dialog.
This will not block the caller, but will instead call callback (if provided) with the result from the dialog.
The dialog will be destroyed once the callback has returned.
@param dialog: The dialog to show.
@type dialog: C{wx.Dialog}
@param callback: The optional callable to call with the result from the dialog.
@type callback: callable

This function is deprecated.
Use :class:`message.MessageDialog` instead.

:param dialog: The dialog to show.
:param callback: The optional callable to call with the result from the dialog.
"""
warnings.warn(
"showScriptModalDialog is deprecated. Use an instance of message.MessageDialog and wx.CallAfter instead.",
DeprecationWarning,
)

def run():
mainFrame.prePopup()
res = dialog.ShowModal()
mainFrame.postPopup()
res = displayDialogAsModal(dialog)
if callback:
callback(res)
dialog.Destroy()
Expand Down
Loading
Loading