4646HTMLDLG_VERIFY = 0x0100
4747
4848
49- def _warnBrowsableMessageNotAvailableOnSecureScreens (title : Optional [ str ] ) -> None :
49+ def _warnBrowsableMessageNotAvailableOnSecureScreens (title : str | None = None ) -> None :
5050 """Warn the user that a browsable message could not be shown on a secure screen (sign-on screen / UAC
5151 prompt).
52- @param title: If provided, the title of the browsable message to give the user more context.
52+
53+ :param title: If provided, the title of the browsable message to give the user more context.
5354 """
5455 log .warning (
5556 "While on secure screens browsable messages can not be used."
@@ -72,68 +73,150 @@ def _warnBrowsableMessageNotAvailableOnSecureScreens(title: Optional[str]) -> No
7273 # The title may be something like "Formatting".
7374 "This feature ({title}) is unavailable while on secure screens"
7475 " such as the sign-on screen or UAC prompt." ,
75- )
76- browsableMessageUnavailableMsg = browsableMessageUnavailableMsg .format (title = title )
76+ ).format (title = title )
7777
7878 import wx # Late import to prevent circular dependency.
7979 import gui # Late import to prevent circular dependency.
8080
8181 log .debug ("Presenting browsable message unavailable warning." )
82- gui .messageBox (
82+ wx .CallAfter (
83+ gui .messageBox ,
84+ browsableMessageUnavailableMsg ,
85+ # Translators: This is the title for a warning dialog, shown if NVDA cannot open a browsable message.
86+ caption = _ ("Feature unavailable." ),
87+ style = wx .ICON_ERROR | wx .OK ,
88+ )
89+
90+
91+ def _warnBrowsableMessageComponentFailure (title : str | None = None ) -> None :
92+ """Warn the user that a browsable message could not be shown because of a component failure.
93+
94+ :param title: If provided, the title of the browsable message to give the user more context.
95+ """
96+ log .warning (
97+ "A browsable message could not be shown because of a component failure."
98+ f" Attempted to open message with title: { title !r} " ,
99+ )
100+
101+ if not title :
102+ browsableMessageUnavailableMsg : str = _ (
103+ # Translators: This is the message for a warning shown if NVDA cannot open a browsable message window
104+ # because of a component failure.
105+ "An error has caused this feature to be unavailable at this time. "
106+ "Restarting NVDA or Windows may solve this problem." ,
107+ )
108+ else :
109+ browsableMessageUnavailableMsg : str = _ (
110+ # Translators: This is the message for a warning shown if NVDA cannot open a browsable message window
111+ # because of a component failure. This prompt includes the title
112+ # of the Window that could not be opened for context.
113+ # The {title} will be replaced with the title.
114+ # The title may be something like "Formatting".
115+ "An error has caused this feature ({title}) to be unavailable at this time. "
116+ "Restarting NVDA or Windows may solve this problem." ,
117+ ).format (title = title )
118+
119+ log .debug ("Presenting browsable message unavailable warning." )
120+ import wx # Late import to prevent circular dependency.
121+ import gui # Late import to prevent circular dependency.
122+
123+ wx .CallAfter (
124+ gui .messageBox ,
83125 browsableMessageUnavailableMsg ,
84- # Translators: This is the title for a warning dialog, shown if NVDA cannot open a browsable message
85- # dialog.
126+ # Translators: This is the title for a warning dialog, shown if NVDA cannot open a browsable message.
86127 caption = _ ("Feature unavailable." ),
87128 style = wx .ICON_ERROR | wx .OK ,
88129 )
89130
90131
91- def browseableMessage (message : str , title : Optional [str ] = None , isHtml : bool = False ) -> None :
132+ def browseableMessage (
133+ message : str ,
134+ title : str | None = None ,
135+ isHtml : bool = False ,
136+ closeButton : bool = False ,
137+ copyButton : bool = False ,
138+ ) -> None :
92139 """Present a message to the user that can be read in browse mode.
93140 The message will be presented in an HTML document.
94- @param message: The message in either html or text.
95- @param title: The title for the message.
96- @param isHtml: Whether the message is html
141+
142+ :param message: The message in either html or text.
143+ :param title: The title for the message, defaults to "NVDA Message".
144+ :param isHtml: Whether the message is html, defaults to False.
145+ :param closeButton: Whether to include a "close" button, defaults to False.
146+ :param copyButton: Whether to include a "copy" (to clipboard) button, defaults to False.
97147 """
98148 if isRunningOnSecureDesktop ():
99- import wx # Late import to prevent circular dependency.
100-
101- wx .CallAfter (_warnBrowsableMessageNotAvailableOnSecureScreens , title )
149+ _warnBrowsableMessageNotAvailableOnSecureScreens (title )
102150 return
103151
104152 htmlFileName = os .path .join (globalVars .appDir , "message.html" )
105153 if not os .path .isfile (htmlFileName ):
154+ _warnBrowsableMessageComponentFailure (title )
106155 raise LookupError (htmlFileName )
156+
107157 moniker = POINTER (IUnknown )()
108- windll .urlmon .CreateURLMonikerEx (0 , htmlFileName , byref (moniker ), URL_MK_UNIFORM )
109- if not title :
110- # Translators: The title for the dialog used to present general NVDA messages in browse mode.
111- title = _ ("NVDA Message" )
112- if not isHtml :
113- message = f"<pre>{ escape (message )} </pre>"
158+ try :
159+ windll .urlmon .CreateURLMonikerEx (0 , htmlFileName , byref (moniker ), URL_MK_UNIFORM )
160+ except OSError as e :
161+ log .error (f"OS error during URL moniker creation: { e } " )
162+ _warnBrowsableMessageComponentFailure (title )
163+ return
164+ except Exception as e :
165+ log .error (f"Unexpected error during URL moniker creation: { e } " )
166+ _warnBrowsableMessageComponentFailure (title )
167+ return
168+
114169 try :
115170 d = comtypes .client .CreateObject ("Scripting.Dictionary" )
116171 except (COMError , OSError ):
117172 log .error ("Scripting.Dictionary component unavailable" , exc_info = True )
118- # Store the module level message function in a new variable since it is masked by a local variable with
119- # the same name
120- messageFunction = globals ()["message" ]
121- # Translators: reported when unable to display a browsable message.
122- messageFunction (_ ("Unable to display browseable message" ))
173+ _warnBrowsableMessageComponentFailure (title )
123174 return
175+
176+ if title is None :
177+ # Translators: The title for the dialog used to present general NVDA messages in browse mode.
178+ title = _ ("NVDA Message" )
124179 d .add ("title" , title )
180+
181+ if not isHtml :
182+ message = f"<pre>{ escape (message )} </pre>"
183+ else :
184+ log .warning ("Passing raw HTML to ui.browseableMessage!" )
125185 d .add ("message" , message )
186+
187+ # Translators: A notice to the user that a copy operation succeeded.
188+ d .add ("copySuccessfulAlertText" , _ ("Text copied." ))
189+ # Translators: A notice to the user that a copy operation failed.
190+ d .add ("copyFailedAlertText" , _ ("Couldn't copy to clipboard." ))
191+ if closeButton :
192+ # Translators: The text of a button which closes the window.
193+ d .add ("closeButtonText" , _ ("Close" ))
194+ if copyButton :
195+ # Translators: The label of a button to copy the text of the window to the clipboard.
196+ d .add ("copyButtonText" , _ ("Copy" ))
197+ # Translators: A portion of an accessibility label for the "Copy" button,
198+ # describing the key to press to activate the button. Currently, this key may only be Ctrl+Shift+C.
199+ # Translation makes sense here if the Control or Shift keys are called something else in a
200+ # given language; or to set this to the empty string if that key combination is unavailable on some keyboard.
201+ d .add ("copyButtonAcceleratorAccessibilityLabel" , _ ("Ctrl+Shift+C" ))
202+
126203 dialogArgsVar = automation .VARIANT (d )
127204 gui .mainFrame .prePopup ()
128- windll .mshtml .ShowHTMLDialogEx (
129- gui .mainFrame .Handle ,
130- moniker ,
131- HTMLDLG_MODELESS ,
132- byref (dialogArgsVar ),
133- DIALOG_OPTIONS ,
134- None ,
135- )
136- gui .mainFrame .postPopup ()
205+ try :
206+ windll .mshtml .ShowHTMLDialogEx (
207+ gui .mainFrame .Handle ,
208+ moniker ,
209+ HTMLDLG_MODELESS ,
210+ byref (dialogArgsVar ),
211+ DIALOG_OPTIONS ,
212+ None ,
213+ )
214+ except Exception as e :
215+ log .error (f"Failed to show HTML dialog: { e } " )
216+ _warnBrowsableMessageComponentFailure (title )
217+ return
218+ finally :
219+ gui .mainFrame .postPopup ()
137220
138221
139222def message (
0 commit comments