diff --git a/source/core.py b/source/core.py index 61173822c29..ca36a04dbf2 100644 --- a/source/core.py +++ b/source/core.py @@ -79,7 +79,8 @@ def doStartupDialogs(): _("Configuration File Error"), wx.OK | wx.ICON_EXCLAMATION) if config.conf["general"]["showWelcomeDialogAtStartup"]: - gui.WelcomeDialog.run() + from gui.startupDialogs import WelcomeDialog + WelcomeDialog.run() if config.conf["brailleViewer"]["showBrailleViewerAtStartup"]: gui.mainFrame.onToggleBrailleViewerCommand(evt=None) if config.conf["speechViewer"]["showSpeechViewerAtStartup"]: @@ -105,7 +106,7 @@ def onResult(ID): except: pass # Ask the user if usage stats can be collected. - gui.runScriptModalDialog(gui.AskAllowUsageStatsDialog(None),onResult) + gui.runScriptModalDialog(gui.startupDialogs.AskAllowUsageStatsDialog(None), onResult) def restart(disableAddons=False, debugLogging=False): """Restarts NVDA by starting a new copy.""" @@ -519,7 +520,8 @@ def handlePowerStatusChange(self): except: log.error("", exc_info=True) if globalVars.appArgs.launcher: - gui.LauncherDialog.run() + from gui.startupDialogs import LauncherDialog + LauncherDialog.run() # LauncherDialog will call doStartupDialogs() afterwards if required. else: wx.CallAfter(doStartupDialogs) diff --git a/source/documentationUtils.py b/source/documentationUtils.py new file mode 100644 index 00000000000..db80df9c5eb --- /dev/null +++ b/source/documentationUtils.py @@ -0,0 +1,52 @@ +# -*- coding: UTF-8 -*- +# A part of NonVisual Desktop Access (NVDA) +# Copyright (C) 2006-2021 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 + +import os +import sys + +import globalVars +import languageHandler + + +def getDocFilePath(fileName, localized=True): + if not getDocFilePath.rootPath: + if hasattr(sys, "frozen"): + getDocFilePath.rootPath = os.path.join(globalVars.appDir, "documentation") + else: + getDocFilePath.rootPath = os.path.join(globalVars.appDir, "..", "user_docs") + + if localized: + lang = languageHandler.getLanguage() + tryLangs = [lang] + if "_" in lang: + # This locale has a sub-locale, but documentation might not exist for the sub-locale, so try stripping it. + tryLangs.append(lang.split("_")[0]) + # If all else fails, use English. + tryLangs.append("en") + + fileName, fileExt = os.path.splitext(fileName) + for tryLang in tryLangs: + tryDir = os.path.join(getDocFilePath.rootPath, tryLang) + if not os.path.isdir(tryDir): + continue + + # Some out of date translations might include .txt files which are now .html files in newer translations. + # Therefore, ignore the extension and try both .html and .txt. + for tryExt in ("html", "txt"): + tryPath = os.path.join(tryDir, f"{fileName}.{tryExt}") + if os.path.isfile(tryPath): + return tryPath + return None + else: + # Not localized. + if not hasattr(sys, "frozen") and fileName in ("copying.txt", "contributors.txt"): + # If running from source, these two files are in the root dir. + return os.path.join(globalVars.appDir, "..", fileName) + else: + return os.path.join(getDocFilePath.rootPath, fileName) + + +getDocFilePath.rootPath = None diff --git a/source/gui/__init__.py b/source/gui/__init__.py index 6cbe8c2ac02..742e8869dcb 100644 --- a/source/gui/__init__.py +++ b/source/gui/__init__.py @@ -5,17 +5,10 @@ # This file is covered by the GNU General Public License. # See the file COPYING for more details. -from .contextHelp import ( - # several other submodules depend on ContextHelpMixin - # ensure early that it can be imported successfully. - ContextHelpMixin as _ContextHelpMixin, # don't expose from gui, import submodule directly. -) - import time import os import sys import threading -import codecs import ctypes import weakref import wx @@ -23,10 +16,10 @@ import globalVars import tones import ui +from documentationUtils import getDocFilePath from logHandler import log import config import versionInfo -import addonAPIVersion import speech import queueHandler import core @@ -34,14 +27,10 @@ from .settingsDialogs import * from .inputGestures import InputGesturesDialog import speechDictHandler -import languageHandler -import keyboardHandler from . import logViewer import speechViewer import winUser import api -from . import guiHelper -import winVersion try: import updateCheck @@ -57,43 +46,6 @@ mainFrame = None isInMessageBox = False -def getDocFilePath(fileName, localized=True): - if not getDocFilePath.rootPath: - if hasattr(sys, "frozen"): - getDocFilePath.rootPath = os.path.join(NVDA_PATH, "documentation") - else: - getDocFilePath.rootPath = os.path.join(NVDA_PATH, "..", "user_docs") - - if localized: - lang = languageHandler.getLanguage() - tryLangs = [lang] - if "_" in lang: - # This locale has a sub-locale, but documentation might not exist for the sub-locale, so try stripping it. - tryLangs.append(lang.split("_")[0]) - # If all else fails, use English. - tryLangs.append("en") - - fileName, fileExt = os.path.splitext(fileName) - for tryLang in tryLangs: - tryDir = os.path.join(getDocFilePath.rootPath, tryLang) - if not os.path.isdir(tryDir): - continue - - # Some out of date translations might include .txt files which are now .html files in newer translations. - # Therefore, ignore the extension and try both .html and .txt. - for tryExt in ("html", "txt"): - tryPath = os.path.join(tryDir, "%s.%s" % (fileName, tryExt)) - if os.path.isfile(tryPath): - return tryPath - return None - else: - # Not localized. - if not hasattr(sys, "frozen") and fileName in ("copying.txt", "contributors.txt"): - # If running from source, these two files are in the root dir. - return os.path.join(NVDA_PATH, "..", fileName) - else: - return os.path.join(getDocFilePath.rootPath, fileName) -getDocFilePath.rootPath = None class MainFrame(wx.Frame): @@ -530,6 +482,7 @@ def __init__(self, frame): self.Bind(wx.EVT_MENU, lambda evt: os.startfile(getDocFilePath("contributors.txt", False)), item) # Translators: The label for the menu item to open NVDA Welcome Dialog. item = menu_help.Append(wx.ID_ANY, _("We&lcome dialog...")) + from .startupDialogs import WelcomeDialog self.Bind(wx.EVT_MENU, lambda evt: WelcomeDialog.run(), item) menu_help.AppendSeparator() if updateCheck: @@ -694,189 +647,6 @@ def run(): wx.CallAfter(run) -class WelcomeDialog( - _ContextHelpMixin, - wx.Dialog # wxPython does not seem to call base class initializer, put last in MRO -): - """The NVDA welcome dialog. - This provides essential information for new users, such as a description of the NVDA key and instructions on how to activate the NVDA menu. - It also provides quick access to some important configuration options. - This dialog is displayed the first time NVDA is started with a new configuration. - """ - helpId = "WelcomeDialog" - WELCOME_MESSAGE_DETAIL = _( - # Translators: The main message for the Welcome dialog when the user starts NVDA for the first time. - "Most commands for controlling NVDA require you to hold down" - " the NVDA key while pressing other keys.\n" - "By default, the numpad Insert and main Insert keys may both be used as the NVDA key.\n" - "You can also configure NVDA to use the CapsLock as the NVDA key.\n" - "Press NVDA+n at any time to activate the NVDA menu.\n" - "From this menu, you can configure NVDA, get help and access other NVDA functions." - ) - - def __init__(self, parent): - # Translators: The title of the Welcome dialog when user starts NVDA for the first time. - super(WelcomeDialog, self).__init__(parent, wx.ID_ANY, _("Welcome to NVDA")) - - mainSizer=wx.BoxSizer(wx.VERTICAL) - # Translators: The header for the Welcome dialog when user starts NVDA for the first time. This is in larger, - # bold lettering - welcomeTextHeader = wx.StaticText(self, label=_("Welcome to NVDA!")) - welcomeTextHeader.SetFont(wx.Font(18, wx.FONTFAMILY_DEFAULT, wx.NORMAL, wx.BOLD)) - mainSizer.AddSpacer(guiHelper.SPACE_BETWEEN_VERTICAL_DIALOG_ITEMS) - mainSizer.Add(welcomeTextHeader,border=20,flag=wx.EXPAND|wx.LEFT|wx.RIGHT) - mainSizer.AddSpacer(guiHelper.SPACE_BETWEEN_VERTICAL_DIALOG_ITEMS) - welcomeTextDetail = wx.StaticText(self, wx.ID_ANY, self.WELCOME_MESSAGE_DETAIL) - mainSizer.Add(welcomeTextDetail,border=20,flag=wx.EXPAND|wx.LEFT|wx.RIGHT) - - # Translators: The label for a group box containing the NVDA welcome dialog options. - optionsLabel = _("Options") - optionsSizer = wx.StaticBoxSizer(wx.VERTICAL, self, label=optionsLabel) - optionsBox = optionsSizer.GetStaticBox() - sHelper = guiHelper.BoxSizerHelper(self, sizer=optionsSizer) - # Translators: The label of a combobox in the Welcome dialog. - kbdLabelText = _("&Keyboard layout:") - layouts = keyboardHandler.KeyboardInputGesture.LAYOUTS - self.kbdNames = sorted(layouts) - kbdChoices = [layouts[layout] for layout in self.kbdNames] - self.kbdList = sHelper.addLabeledControl(kbdLabelText, wx.Choice, choices=kbdChoices) - try: - index = self.kbdNames.index(config.conf["keyboard"]["keyboardLayout"]) - self.kbdList.SetSelection(index) - except: - log.error("Could not set Keyboard layout list to current layout",exc_info=True) - # Translators: The label of a checkbox in the Welcome dialog. - capsAsNVDAModifierText = _("&Use CapsLock as an NVDA modifier key") - self.capsAsNVDAModifierCheckBox = sHelper.addItem(wx.CheckBox(optionsBox, label=capsAsNVDAModifierText)) - self.capsAsNVDAModifierCheckBox.SetValue(config.conf["keyboard"]["useCapsLockAsNVDAModifierKey"]) - # Translators: The label of a checkbox in the Welcome dialog. - startAfterLogonText = _("St&art NVDA after I sign in") - self.startAfterLogonCheckBox = sHelper.addItem(wx.CheckBox(optionsBox, label=startAfterLogonText)) - self.startAfterLogonCheckBox.Value = config.getStartAfterLogon() - if globalVars.appArgs.secure or config.isAppX or not config.isInstalledCopy(): - self.startAfterLogonCheckBox.Disable() - # Translators: The label of a checkbox in the Welcome dialog. - showWelcomeDialogAtStartupText = _("&Show this dialog when NVDA starts") - _showWelcomeDialogAtStartupCheckBox = wx.CheckBox(optionsBox, label=showWelcomeDialogAtStartupText) - self.showWelcomeDialogAtStartupCheckBox = sHelper.addItem(_showWelcomeDialogAtStartupCheckBox) - self.showWelcomeDialogAtStartupCheckBox.SetValue(config.conf["general"]["showWelcomeDialogAtStartup"]) - mainSizer.Add(optionsSizer, border=guiHelper.BORDER_FOR_DIALOGS, flag=wx.ALL) - mainSizer.Add(self.CreateButtonSizer(wx.OK), border=guiHelper.BORDER_FOR_DIALOGS, flag=wx.ALL|wx.ALIGN_RIGHT) - self.Bind(wx.EVT_BUTTON, self.onOk, id=wx.ID_OK) - - mainSizer.Fit(self) - self.SetSizer(mainSizer) - self.kbdList.SetFocus() - self.CentreOnScreen() - - def onOk(self, evt): - layout = self.kbdNames[self.kbdList.GetSelection()] - config.conf["keyboard"]["keyboardLayout"] = layout - config.conf["keyboard"]["useCapsLockAsNVDAModifierKey"] = self.capsAsNVDAModifierCheckBox.IsChecked() - if self.startAfterLogonCheckBox.Enabled: - config.setStartAfterLogon(self.startAfterLogonCheckBox.Value) - config.conf["general"]["showWelcomeDialogAtStartup"] = self.showWelcomeDialogAtStartupCheckBox.IsChecked() - try: - config.conf.save() - except: - log.debugWarning("Could not save",exc_info=True) - self.EndModal(wx.ID_OK) - - @classmethod - def run(cls): - """Prepare and display an instance of this dialog. - This does not require the dialog to be instantiated. - """ - mainFrame.prePopup() - d = cls(mainFrame) - d.ShowModal() - d.Destroy() - mainFrame.postPopup() - - -class LauncherDialog( - _ContextHelpMixin, - wx.Dialog # wxPython does not seem to call base class initializer, put last in MRO -): - """The dialog that is displayed when NVDA is started from the launcher. - This displays the license and allows the user to install or create a portable copy of NVDA. - """ - helpId = "InstallingNVDA" - - def __init__(self, parent): - super(LauncherDialog, self).__init__(parent, title=versionInfo.name) - - mainSizer = wx.BoxSizer(wx.VERTICAL) - sHelper = guiHelper.BoxSizerHelper(self, orientation=wx.VERTICAL) - - # Translators: The label of the license text which will be shown when NVDA installation program starts. - groupLabel = _("License Agreement") - sizer = wx.StaticBoxSizer(wx.VERTICAL, self, label=groupLabel) - sHelper.addItem(sizer) - licenseTextCtrl = wx.TextCtrl(self, size=(500, 400), style=wx.TE_MULTILINE | wx.TE_READONLY | wx.TE_RICH) - licenseTextCtrl.Value = codecs.open(getDocFilePath("copying.txt", False), "r", encoding="UTF-8").read() - sizer.Add(licenseTextCtrl) - - # Translators: The label for a checkbox in NvDA installation program to agree to the license agreement. - agreeText = _("I &agree") - self.licenseAgreeCheckbox = sHelper.addItem(wx.CheckBox(self, label=agreeText)) - self.licenseAgreeCheckbox.Value = False - self.licenseAgreeCheckbox.Bind(wx.EVT_CHECKBOX, self.onLicenseAgree) - - sizer = sHelper.addItem(wx.GridSizer(2, 2, 0, 0)) - self.actionButtons = [] - # Translators: The label of the button in NVDA installation program to install NvDA on the user's computer. - ctrl = wx.Button(self, label=_("&Install NVDA on this computer")) - sizer.Add(ctrl) - ctrl.Bind(wx.EVT_BUTTON, lambda evt: self.onAction(evt, mainFrame.onInstallCommand)) - self.actionButtons.append(ctrl) - # Translators: The label of the button in NVDA installation program to create a portable version of NVDA. - ctrl = wx.Button(self, label=_("Create &portable copy")) - sizer.Add(ctrl) - ctrl.Bind(wx.EVT_BUTTON, lambda evt: self.onAction(evt, mainFrame.onCreatePortableCopyCommand)) - self.actionButtons.append(ctrl) - # Translators: The label of the button in NVDA installation program to continue using the installation program as a temporary copy of NVDA. - ctrl = wx.Button(self, label=_("&Continue running")) - sizer.Add(ctrl) - ctrl.Bind(wx.EVT_BUTTON, self.onContinueRunning) - self.actionButtons.append(ctrl) - sizer.Add(wx.Button(self, label=_("E&xit"), id=wx.ID_CANCEL)) - # If we bind this on the button, it fails to trigger when the dialog is closed. - self.Bind(wx.EVT_BUTTON, self.onExit, id=wx.ID_CANCEL) - - for ctrl in self.actionButtons: - ctrl.Disable() - - mainSizer.Add(sHelper.sizer, border = guiHelper.BORDER_FOR_DIALOGS, flag=wx.ALL) - self.Sizer = mainSizer - mainSizer.Fit(self) - self.CentreOnScreen() - - def onLicenseAgree(self, evt): - for ctrl in self.actionButtons: - ctrl.Enable(evt.IsChecked()) - - def onAction(self, evt, func): - self.Destroy() - func(evt) - - def onContinueRunning(self, evt): - self.Destroy() - core.doStartupDialogs() - - def onExit(self, evt): - wx.GetApp().ExitMainLoop() - - @classmethod - def run(cls): - """Prepare and display an instance of this dialog. - This does not require the dialog to be instantiated. - """ - mainFrame.prePopup() - d = cls(mainFrame) - d.Show() - mainFrame.postPopup() - class ExitDialog(wx.Dialog): _instance = None @@ -1087,67 +857,3 @@ def Notify(self): def _isDebug(): return config.conf["debugLog"]["gui"] - - -class AskAllowUsageStatsDialog( - _ContextHelpMixin, - wx.Dialog # wxPython does not seem to call base class initializer, put last in MRO -): - """A dialog asking if the user wishes to allow NVDA usage stats to be collected by NV Access.""" - - helpId = "UsageStatsDialog" - - def __init__(self, parent): - # Translators: The title of the dialog asking if usage data can be collected - super().__init__(parent, title=_("NVDA Usage Data Collection")) - mainSizer = wx.BoxSizer(wx.VERTICAL) - sHelper = guiHelper.BoxSizerHelper(self, orientation=wx.VERTICAL) - - # Translators: A message asking the user if they want to allow usage stats gathering - message=_("In order to improve NVDA in the future, NV Access wishes to collect usage data from running copies of NVDA.\n\n" - "Data includes Operating System version, NVDA version, language, country of origin, plus certain NVDA configuration such as current synthesizer, braille display and braille table. " - "No spoken or braille content will be ever sent to NV Access. Please refer to the User Guide for a current list of all data collected.\n\n" - "Do you wish to allow NV Access to periodically collect this data in order to improve NVDA?") - sText=sHelper.addItem(wx.StaticText(self, label=message)) - # the wx.Window must be constructed before we can get the handle. - import windowUtils - self.scaleFactor = windowUtils.getWindowScalingFactor(self.GetHandle()) - sText.Wrap(self.scaleFactor*600) # 600 was fairly arbitrarily chosen by a visual user to look acceptable on their machine. - - bHelper = sHelper.addDialogDismissButtons(guiHelper.ButtonHelper(wx.HORIZONTAL)) - - # Translators: The label of a Yes button in a dialog - yesButton = bHelper.addButton(self, wx.ID_YES, label=_("&Yes")) - yesButton.Bind(wx.EVT_BUTTON, self.onYesButton) - - # Translators: The label of a No button in a dialog - noButton = bHelper.addButton(self, wx.ID_NO, label=_("&No")) - noButton.Bind(wx.EVT_BUTTON, self.onNoButton) - - # Translators: The label of a button to remind the user later about performing some action. - remindMeButton = bHelper.addButton(self, wx.ID_CANCEL, label=_("Remind me &later")) - remindMeButton.Bind(wx.EVT_BUTTON, self.onLaterButton) - remindMeButton.SetFocus() - - mainSizer.Add(sHelper.sizer, border=guiHelper.BORDER_FOR_DIALOGS, flag=wx.ALL) - self.Sizer = mainSizer - mainSizer.Fit(self) - self.Center(wx.BOTH | wx.CENTER_ON_SCREEN) - - def onYesButton(self,evt): - log.debug("Usage stats gathering has been allowed") - config.conf['update']['askedAllowUsageStats']=True - config.conf['update']['allowUsageStats']=True - self.EndModal(wx.ID_YES) - - def onNoButton(self,evt): - log.debug("Usage stats gathering has been disallowed") - config.conf['update']['askedAllowUsageStats']=True - config.conf['update']['allowUsageStats']=False - self.EndModal(wx.ID_NO) - - def onLaterButton(self,evt): - log.debug("Usage stats gathering question has been deferred") - # evt.Skip() is called since wx.ID_CANCEL is used as the ID for the Ask Later button, - # wx automatically ends the modal itself. - evt.Skip() diff --git a/source/gui/contextHelp.py b/source/gui/contextHelp.py index 5c562d25f7a..b9b2a7d54bb 100644 --- a/source/gui/contextHelp.py +++ b/source/gui/contextHelp.py @@ -9,6 +9,7 @@ import wx from logHandler import log +import documentationUtils def writeRedirect(helpId: str, helpFilePath: str, contextHelpPath: str): @@ -34,9 +35,7 @@ def showHelp(helpId: str): noHelpMessage = _("No help available here.") queueHandler.queueFunction(queueHandler.eventQueue, ui.message, noHelpMessage) return - - import gui - helpFile = gui.getDocFilePath("userGuide.html") + helpFile = documentationUtils.getDocFilePath("userGuide.html") if helpFile is None: # Translators: Message shown when trying to display context sensitive help, # indicating that the user guide could not be found. diff --git a/source/gui/startupDialogs.py b/source/gui/startupDialogs.py new file mode 100644 index 00000000000..a95ce8fde60 --- /dev/null +++ b/source/gui/startupDialogs.py @@ -0,0 +1,278 @@ +# -*- coding: UTF-8 -*- +# A part of NonVisual Desktop Access (NVDA) +# Copyright (C) 2006-2021 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 + +import wx + +import config +import core +from documentationUtils import getDocFilePath +import globalVars +import gui +import keyboardHandler +from logHandler import log +import versionInfo + + +class WelcomeDialog( + gui.contextHelp.ContextHelpMixin, + wx.Dialog # wxPython does not seem to call base class initializer, put last in MRO +): + """The NVDA welcome dialog. + This provides essential information for new users, + such as a description of the NVDA key and instructions on how to activate the NVDA menu. + It also provides quick access to some important configuration options. + This dialog is displayed the first time NVDA is started with a new configuration. + """ + helpId = "WelcomeDialog" + WELCOME_MESSAGE_DETAIL = _( + # Translators: The main message for the Welcome dialog when the user starts NVDA for the first time. + "Most commands for controlling NVDA require you to hold down" + " the NVDA key while pressing other keys.\n" + "By default, the numpad Insert and main Insert keys may both be used as the NVDA key.\n" + "You can also configure NVDA to use the CapsLock as the NVDA key.\n" + "Press NVDA+n at any time to activate the NVDA menu.\n" + "From this menu, you can configure NVDA, get help and access other NVDA functions." + ) + + def __init__(self, parent): + # Translators: The title of the Welcome dialog when user starts NVDA for the first time. + super().__init__(parent, wx.ID_ANY, _("Welcome to NVDA")) + + mainSizer = wx.BoxSizer(wx.VERTICAL) + # Translators: The header for the Welcome dialog when user starts NVDA for the first time. + # This is in larger, bold lettering + welcomeTextHeader = wx.StaticText(self, label=_("Welcome to NVDA!")) + welcomeTextHeader.SetFont(wx.Font(18, wx.FONTFAMILY_DEFAULT, wx.NORMAL, wx.BOLD)) + mainSizer.AddSpacer(gui.guiHelper.SPACE_BETWEEN_VERTICAL_DIALOG_ITEMS) + mainSizer.Add(welcomeTextHeader, border=20, flag=wx.EXPAND | wx.LEFT | wx.RIGHT) + mainSizer.AddSpacer(gui.guiHelper.SPACE_BETWEEN_VERTICAL_DIALOG_ITEMS) + welcomeTextDetail = wx.StaticText(self, wx.ID_ANY, self.WELCOME_MESSAGE_DETAIL) + mainSizer.Add(welcomeTextDetail, border=20, flag=wx.EXPAND | wx.LEFT | wx.RIGHT) + + # Translators: The label for a group box containing the NVDA welcome dialog options. + optionsLabel = _("Options") + optionsSizer = wx.StaticBoxSizer(wx.VERTICAL, self, label=optionsLabel) + optionsBox = optionsSizer.GetStaticBox() + sHelper = gui.guiHelper.BoxSizerHelper(self, sizer=optionsSizer) + # Translators: The label of a combobox in the Welcome dialog. + kbdLabelText = _("&Keyboard layout:") + layouts = keyboardHandler.KeyboardInputGesture.LAYOUTS + self.kbdNames = sorted(layouts) + kbdChoices = [layouts[layout] for layout in self.kbdNames] + self.kbdList = sHelper.addLabeledControl(kbdLabelText, wx.Choice, choices=kbdChoices) + try: + index = self.kbdNames.index(config.conf["keyboard"]["keyboardLayout"]) + self.kbdList.SetSelection(index) + except (ValueError, KeyError): + log.error("Could not set Keyboard layout list to current layout", exc_info=True) + # Translators: The label of a checkbox in the Welcome dialog. + capsAsNVDAModifierText = _("&Use CapsLock as an NVDA modifier key") + self.capsAsNVDAModifierCheckBox = sHelper.addItem(wx.CheckBox(optionsBox, label=capsAsNVDAModifierText)) + self.capsAsNVDAModifierCheckBox.SetValue(config.conf["keyboard"]["useCapsLockAsNVDAModifierKey"]) + # Translators: The label of a checkbox in the Welcome dialog. + startAfterLogonText = _("St&art NVDA after I sign in") + self.startAfterLogonCheckBox = sHelper.addItem(wx.CheckBox(optionsBox, label=startAfterLogonText)) + self.startAfterLogonCheckBox.Value = config.getStartAfterLogon() + if globalVars.appArgs.secure or config.isAppX or not config.isInstalledCopy(): + self.startAfterLogonCheckBox.Disable() + # Translators: The label of a checkbox in the Welcome dialog. + showWelcomeDialogAtStartupText = _("&Show this dialog when NVDA starts") + _showWelcomeDialogAtStartupCheckBox = wx.CheckBox(optionsBox, label=showWelcomeDialogAtStartupText) + self.showWelcomeDialogAtStartupCheckBox = sHelper.addItem(_showWelcomeDialogAtStartupCheckBox) + self.showWelcomeDialogAtStartupCheckBox.SetValue(config.conf["general"]["showWelcomeDialogAtStartup"]) + mainSizer.Add(optionsSizer, border=gui.guiHelper.BORDER_FOR_DIALOGS, flag=wx.ALL) + mainSizer.Add( + self.CreateButtonSizer(wx.OK), + border=gui.guiHelper.BORDER_FOR_DIALOGS, + flag=wx.ALL | wx.ALIGN_RIGHT + ) + self.Bind(wx.EVT_BUTTON, self.onOk, id=wx.ID_OK) + + mainSizer.Fit(self) + self.SetSizer(mainSizer) + self.kbdList.SetFocus() + self.CentreOnScreen() + + def onOk(self, evt): + layout = self.kbdNames[self.kbdList.GetSelection()] + config.conf["keyboard"]["keyboardLayout"] = layout + config.conf["keyboard"]["useCapsLockAsNVDAModifierKey"] = self.capsAsNVDAModifierCheckBox.IsChecked() + if self.startAfterLogonCheckBox.Enabled: + config.setStartAfterLogon(self.startAfterLogonCheckBox.Value) + config.conf["general"]["showWelcomeDialogAtStartup"] = self.showWelcomeDialogAtStartupCheckBox.IsChecked() + try: + config.conf.save() + except Exception: + log.debugWarning("Could not save", exc_info=True) + self.EndModal(wx.ID_OK) + + @classmethod + def run(cls): + """Prepare and display an instance of this dialog. + This does not require the dialog to be instantiated. + """ + gui.mainFrame.prePopup() + d = cls(gui.mainFrame) + d.ShowModal() + d.Destroy() + gui.mainFrame.postPopup() + + +class LauncherDialog( + gui.contextHelp.ContextHelpMixin, + wx.Dialog # wxPython does not seem to call base class initializer, put last in MRO +): + """The dialog that is displayed when NVDA is started from the launcher. + This displays the license and allows the user to install or create a portable copy of NVDA. + """ + helpId = "InstallingNVDA" + + def __init__(self, parent): + super().__init__(parent, title=versionInfo.name) + + mainSizer = wx.BoxSizer(wx.VERTICAL) + sHelper = gui.guiHelper.BoxSizerHelper(self, orientation=wx.VERTICAL) + + # Translators: The label of the license text which will be shown when NVDA installation program starts. + groupLabel = _("License Agreement") + sizer = wx.StaticBoxSizer(wx.VERTICAL, self, label=groupLabel) + sHelper.addItem(sizer) + licenseTextCtrl = wx.TextCtrl(self, size=(500, 400), style=wx.TE_MULTILINE | wx.TE_READONLY | wx.TE_RICH) + licenseTextCtrl.Value = open(getDocFilePath("copying.txt", False), "r", encoding="UTF-8").read() + sizer.Add(licenseTextCtrl) + + # Translators: The label for a checkbox in NvDA installation program to agree to the license agreement. + agreeText = _("I &agree") + self.licenseAgreeCheckbox = sHelper.addItem(wx.CheckBox(self, label=agreeText)) + self.licenseAgreeCheckbox.Value = False + self.licenseAgreeCheckbox.Bind(wx.EVT_CHECKBOX, self.onLicenseAgree) + + sizer = sHelper.addItem(wx.GridSizer(2, 2, 0, 0)) + self.actionButtons = [] + # Translators: The label of the button in NVDA installation program to install NvDA on the user's computer. + ctrl = wx.Button(self, label=_("&Install NVDA on this computer")) + sizer.Add(ctrl) + ctrl.Bind(wx.EVT_BUTTON, lambda evt: self.onAction(evt, gui.mainFrame.onInstallCommand)) + self.actionButtons.append(ctrl) + # Translators: The label of the button in NVDA installation program to create a portable version of NVDA. + ctrl = wx.Button(self, label=_("Create &portable copy")) + sizer.Add(ctrl) + ctrl.Bind(wx.EVT_BUTTON, lambda evt: self.onAction(evt, gui.mainFrame.onCreatePortableCopyCommand)) + self.actionButtons.append(ctrl) + # Translators: The label of the button in NVDA installation program + # to continue using the installation program as a temporary copy of NVDA. + ctrl = wx.Button(self, label=_("&Continue running")) + sizer.Add(ctrl) + ctrl.Bind(wx.EVT_BUTTON, self.onContinueRunning) + self.actionButtons.append(ctrl) + sizer.Add(wx.Button(self, label=_("E&xit"), id=wx.ID_CANCEL)) + # If we bind this on the button, it fails to trigger when the dialog is closed. + self.Bind(wx.EVT_BUTTON, self.onExit, id=wx.ID_CANCEL) + + for ctrl in self.actionButtons: + ctrl.Disable() + + mainSizer.Add(sHelper.sizer, border=gui.guiHelper.BORDER_FOR_DIALOGS, flag=wx.ALL) + self.Sizer = mainSizer + mainSizer.Fit(self) + self.CentreOnScreen() + + def onLicenseAgree(self, evt): + for ctrl in self.actionButtons: + ctrl.Enable(evt.IsChecked()) + + def onAction(self, evt, func): + self.Destroy() + func(evt) + + def onContinueRunning(self, evt): + self.Destroy() + core.doStartupDialogs() + + def onExit(self, evt): + wx.GetApp().ExitMainLoop() + + @classmethod + def run(cls): + """Prepare and display an instance of this dialog. + This does not require the dialog to be instantiated. + """ + gui.mainFrame.prePopup() + d = cls(gui.mainFrame) + d.Show() + gui.mainFrame.postPopup() + + +class AskAllowUsageStatsDialog( + gui.contextHelp.ContextHelpMixin, + wx.Dialog # wxPython does not seem to call base class initializer, put last in MRO +): + """A dialog asking if the user wishes to allow NVDA usage stats to be collected by NV Access.""" + + helpId = "UsageStatsDialog" + + def __init__(self, parent): + # Translators: The title of the dialog asking if usage data can be collected + super().__init__(parent, title=_("NVDA Usage Data Collection")) + mainSizer = wx.BoxSizer(wx.VERTICAL) + sHelper = gui.guiHelper.BoxSizerHelper(self, orientation=wx.VERTICAL) + + message = _( + # Translators: A message asking the user if they want to allow usage stats gathering + "In order to improve NVDA in the future, " + "NV Access wishes to collect usage data from running copies of NVDA.\n\n" + "Data includes Operating System version, NVDA version, language, country of origin, plus " + "certain NVDA configuration such as current synthesizer, braille display and braille table. " + "No spoken or braille content will be ever sent to NV Access. " + "Please refer to the User Guide for a current list of all data collected.\n\n" + "Do you wish to allow NV Access to periodically collect this data in order to improve NVDA?" + ) + sText = sHelper.addItem(wx.StaticText(self, label=message)) + # the wx.Window must be constructed before we can get the handle. + import windowUtils + self.scaleFactor = windowUtils.getWindowScalingFactor(self.GetHandle()) + sText.Wrap( + # 600 was fairly arbitrarily chosen by a visual user to look acceptable on their machine. + self.scaleFactor * 600 + ) + + bHelper = sHelper.addDialogDismissButtons(gui.guiHelper.ButtonHelper(wx.HORIZONTAL)) + + # Translators: The label of a Yes button in a dialog + yesButton = bHelper.addButton(self, wx.ID_YES, label=_("&Yes")) + yesButton.Bind(wx.EVT_BUTTON, self.onYesButton) + + # Translators: The label of a No button in a dialog + noButton = bHelper.addButton(self, wx.ID_NO, label=_("&No")) + noButton.Bind(wx.EVT_BUTTON, self.onNoButton) + + # Translators: The label of a button to remind the user later about performing some action. + remindMeButton = bHelper.addButton(self, wx.ID_CANCEL, label=_("Remind me &later")) + remindMeButton.Bind(wx.EVT_BUTTON, self.onLaterButton) + remindMeButton.SetFocus() + + mainSizer.Add(sHelper.sizer, border=gui.guiHelper.BORDER_FOR_DIALOGS, flag=wx.ALL) + self.Sizer = mainSizer + mainSizer.Fit(self) + self.Center(wx.BOTH | wx.CENTER_ON_SCREEN) + + def onYesButton(self, evt): + log.debug("Usage stats gathering has been allowed") + config.conf['update']['askedAllowUsageStats'] = True + config.conf['update']['allowUsageStats'] = True + self.EndModal(wx.ID_YES) + + def onNoButton(self, evt): + log.debug("Usage stats gathering has been disallowed") + config.conf['update']['askedAllowUsageStats'] = True + config.conf['update']['allowUsageStats'] = False + self.EndModal(wx.ID_NO) + + def onLaterButton(self, evt): + log.debug("Usage stats gathering question has been deferred") + # evt.Skip() is called since wx.ID_CANCEL is used as the ID for the Ask Later button, + # wx automatically ends the modal itself. + evt.Skip() diff --git a/user_docs/en/changes.t2t b/user_docs/en/changes.t2t index 7d782ab1f69..a430c2a9903 100644 --- a/user_docs/en/changes.t2t +++ b/user_docs/en/changes.t2t @@ -86,6 +86,8 @@ What's New in NVDA - Support of `TextInfo`s that do not inherit from `contentRecog.BaseContentRecogTextInfo` is removed. (#12157) - `speech.speakWithoutPauses` has been removed - please use `speech.SpeechWithoutPauses(speakFunc=speech.speak).speakWithoutPauses` instead. (#12195) - `speech.re_last_pause` has been removed - please use `speech.SpeechWithoutPauses.re_last_pause` instead. (#12195) +- `WelcomeDialog`, `LauncherDialog` and `AskAllowUsageStatsDialog` are moved to the `gui.startupDialogs`. (#12105) +- `getDocFilePath` has been moved from `gui` to the `documentationUtils` module. (#12105) = 2020.4 =