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

Fix right-to-left layout direction issues #12181

Merged
merged 17 commits into from
Mar 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
40 changes: 25 additions & 15 deletions source/core.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# -*- coding: UTF-8 -*-
# A part of NonVisual Desktop Access (NVDA)
# Copyright (C) 2006-2019 NV Access Limited, Aleksey Sadovoy, Christopher Toth, Joseph Lee, Peter Vágner,
# Copyright (C) 2006-2021 NV Access Limited, Aleksey Sadovoy, Christopher Toth, Joseph Lee, Peter Vágner,
# Derek Riemer, Babbage B.V., Zahari Yurukov, Łukasz Golonka
# This file is covered by the GNU General Public License.
# See the file COPYING for more details.

from typing import Optional

"""NVDA core"""

RPC_E_CALL_CANCELED = -2147418110
Expand Down Expand Up @@ -207,6 +208,27 @@ def _setInitialFocus():
except:
log.exception("Error retrieving initial focus")


def getWxLangOrNone() -> Optional['wx.LanguageInfo']:
import languageHandler
import wx
lang = languageHandler.getLanguage()
locale = wx.Locale()
wxLang = locale.FindLanguageInfo(lang)
if not wxLang and '_' in lang:
wxLang = locale.FindLanguageInfo(lang.split('_')[0])
# #8064: Wx might know the language, but may not actually contain a translation database for that language.
# If we try to initialize this language, wx will show a warning dialog.
# #9089: some languages (such as Aragonese) do not have language info, causing language getter to fail.
# In this case, wxLang is already set to None.
# Therefore treat these situations like wx not knowing the language at all.
if wxLang and not locale.IsAvailable(wxLang.Language):
wxLang = None
if not wxLang:
log.debugWarning("wx does not support language %s" % lang)
return wxLang


def main():
"""NVDA's core main loop.
This initializes all modules such as audio, IAccessible, keyboard, mouse, and GUI.
Expand Down Expand Up @@ -418,26 +440,14 @@ def handlePowerStatusChange(self):

# initialize wxpython localization support
locale = wx.Locale()
lang=languageHandler.getLanguage()
wxLang=locale.FindLanguageInfo(lang)
if not wxLang and '_' in lang:
wxLang=locale.FindLanguageInfo(lang.split('_')[0])
wxLang = getWxLangOrNone()
if hasattr(sys,'frozen'):
locale.AddCatalogLookupPathPrefix(os.path.join(globalVars.appDir, "locale"))
# #8064: Wx might know the language, but may not actually contain a translation database for that language.
# If we try to initialize this language, wx will show a warning dialog.
# #9089: some languages (such as Aragonese) do not have language info, causing language getter to fail.
# In this case, wxLang is already set to None.
# Therefore treat these situations like wx not knowing the language at all.
if wxLang and not locale.IsAvailable(wxLang.Language):
wxLang=None
if wxLang:
try:
locale.Init(wxLang.Language)
except:
log.error("Failed to initialize wx locale",exc_info=True)
else:
log.debugWarning("wx does not support language %s" % lang)

log.debug("Initializing garbageHandler")
garbageHandler.initialize()
Expand Down
26 changes: 14 additions & 12 deletions source/gui/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,10 @@ def initialize():
if mainFrame:
raise RuntimeError("GUI already initialized")
mainFrame = MainFrame()
wxLang = core.getWxLangOrNone()
if wxLang:
# otherwise the system default will be used
mainFrame.SetLayoutDirection(wxLang.LayoutDirection)
wx.GetApp().SetTopWindow(mainFrame)
# In wxPython >= 4.1,
# wx.CallAfter no longer executes callbacks while NVDA's main thread is within apopup menu or message box.
Expand Down Expand Up @@ -725,14 +729,10 @@ def __init__(self, parent):
welcomeTextDetail = wx.StaticText(self, wx.ID_ANY, self.WELCOME_MESSAGE_DETAIL)
mainSizer.Add(welcomeTextDetail,border=20,flag=wx.EXPAND|wx.LEFT|wx.RIGHT)

optionsSizer = wx.StaticBoxSizer(
wx.StaticBox(
self,
# Translators: The label for a group box containing the NVDA welcome dialog options.
label=_("Options")
),
wx.VERTICAL
)
# 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:")
Expand All @@ -747,17 +747,18 @@ def __init__(self, parent):
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(self, label=capsAsNVDAModifierText))
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(self, label=startAfterLogonText))
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")
self.showWelcomeDialogAtStartupCheckBox = sHelper.addItem(wx.CheckBox(self, label=showWelcomeDialogAtStartupText))
_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)
Expand Down Expand Up @@ -810,7 +811,8 @@ def __init__(self, parent):

# Translators: The label of the license text which will be shown when NVDA installation program starts.
groupLabel = _("License Agreement")
sizer = sHelper.addItem(wx.StaticBoxSizer(wx.StaticBox(self, label=groupLabel), wx.VERTICAL))
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)
Expand Down
17 changes: 10 additions & 7 deletions source/gui/configProfiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,21 +42,24 @@ def __init__(self, parent):

mainSizer = wx.BoxSizer(wx.VERTICAL)
sHelper = guiHelper.BoxSizerHelper(self,orientation=wx.VERTICAL)
profilesListGroupSizer = wx.StaticBoxSizer(wx.StaticBox(self), wx.HORIZONTAL)
profilesListGroupSizer = wx.StaticBoxSizer(wx.HORIZONTAL, self)
profilesListBox = profilesListGroupSizer.GetStaticBox()
profilesListGroupContents = wx.BoxSizer(wx.HORIZONTAL)

#contains the profile list and activation button in vertical arrangement.
changeProfilesSizer = wx.BoxSizer(wx.VERTICAL)
item = self.profileList = wx.ListBox(self,
choices=[self.getProfileDisplay(name, includeStates=True) for name in self.profileNames])
item = self.profileList = wx.ListBox(
profilesListBox,
choices=[self.getProfileDisplay(name, includeStates=True) for name in self.profileNames]
)
self.bindHelpEvent("ProfilesBasicManagement", self.profileList)
item.Bind(wx.EVT_LISTBOX, self.onProfileListChoice)
item.Selection = self.profileNames.index(config.conf.profiles[-1].name)
changeProfilesSizer.Add(item, proportion=1.0)

changeProfilesSizer.AddSpacer(guiHelper.SPACE_BETWEEN_BUTTONS_VERTICAL)

self.changeStateButton = wx.Button(self)
self.changeStateButton = wx.Button(profilesListBox)
self.bindHelpEvent("ConfigProfileManual", self.changeStateButton)
self.changeStateButton.Bind(wx.EVT_BUTTON, self.onChangeState)
self.AffirmativeId = self.changeStateButton.Id
Expand All @@ -68,17 +71,17 @@ def __init__(self, parent):

buttonHelper = guiHelper.ButtonHelper(wx.VERTICAL)
# Translators: The label of a button to create a new configuration profile.
newButton = buttonHelper.addButton(self, label=_("&New"))
newButton = buttonHelper.addButton(profilesListBox, label=_("&New"))
self.bindHelpEvent("ProfilesCreating", newButton)
newButton.Bind(wx.EVT_BUTTON, self.onNew)

# Translators: The label of a button to rename a configuration profile.
self.renameButton = buttonHelper.addButton(self, label=_("&Rename"))
self.renameButton = buttonHelper.addButton(profilesListBox, label=_("&Rename"))
self.bindHelpEvent("ProfilesBasicManagement", self.renameButton)
self.renameButton.Bind(wx.EVT_BUTTON, self.onRename)

# Translators: The label of a button to delete a configuration profile.
self.deleteButton = buttonHelper.addButton(self, label=_("&Delete"))
self.deleteButton = buttonHelper.addButton(profilesListBox, label=_("&Delete"))
self.bindHelpEvent("ProfilesBasicManagement", self.deleteButton)
self.deleteButton.Bind(wx.EVT_BUTTON, self.onDelete)

Expand Down
13 changes: 9 additions & 4 deletions source/gui/guiHelper.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,10 @@ def addLabeledControl(self, labelText, wxCtrlClass, **kwargs):
Relies on guiHelper.LabeledControlHelper and thus guiHelper.associateElements, and therefore inherits any
limitations from there.
"""
labeledControl = LabeledControlHelper(self._parent, labelText, wxCtrlClass, **kwargs)
parent = self._parent
if isinstance(self.sizer, wx.StaticBoxSizer):
parent = self.sizer.GetStaticBox()
labeledControl = LabeledControlHelper(parent, labelText, wxCtrlClass, **kwargs)
if(isinstance(labeledControl.control, (wx.ListCtrl,wx.ListBox,wx.TreeCtrl))):
self.addItem(labeledControl.sizer, flag=wx.EXPAND, proportion=1)
else:
Expand All @@ -345,6 +348,9 @@ def addDialogDismissButtons(self, buttons, separated=False):
Should be set to L{False} for message or single input dialogs, L{True} otherwise.
@type separated: L{bool}
"""
parent = self._parent
if isinstance(self.sizer, wx.StaticBoxSizer):
parent = self.sizer.GetStaticBox()
if self.sizer.GetOrientation() != wx.VERTICAL:
raise NotImplementedError(
"Adding dialog dismiss buttons to a horizontal BoxSizerHelper is not implemented."
Expand All @@ -354,16 +360,15 @@ def addDialogDismissButtons(self, buttons, separated=False):
elif isinstance(buttons, (wx.Sizer, wx.Button)):
toAdd = buttons
elif isinstance(buttons, int):
toAdd = self._parent.CreateButtonSizer(buttons)
toAdd = parent.CreateButtonSizer(buttons)
else:
raise NotImplementedError("Unknown type: {}".format(buttons))
if separated:
self.addItem(wx.StaticLine(self._parent), flag=wx.EXPAND)
self.addItem(wx.StaticLine(parent), flag=wx.EXPAND)
self.addItem(toAdd, flag=wx.ALIGN_RIGHT)
self.dialogDismissButtonsAdded = True
return buttons

class SIPABCMeta(wx.siplib.wrappertype, ABCMeta):
"""Meta class to be used for wx subclasses with abstract methods."""
pass

45 changes: 24 additions & 21 deletions source/gui/installerGui.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,18 +175,15 @@ def __init__(self, parent, isUpdate):
self.bindHelpEvent("InstallWithIncompatibleAddons", self.confirmationCheckbox)
self.confirmationCheckbox.SetFocus()

optionsSizer = guiHelper.BoxSizerHelper(self, sizer=sHelper.addItem(wx.StaticBoxSizer(
wx.StaticBox(
self,
# Translators: The label for a group box containing the NVDA installation dialog options.
label=_("Options")
),
wx.VERTICAL
)))
# Translators: The label for a group box containing the NVDA installation dialog options.
optionsLabel = _("Options")
optionsHelper = sHelper.addItem(wx.StaticBoxSizer(wx.VERTICAL, self, label=optionsLabel))
optionsSizer = guiHelper.BoxSizerHelper(self, sizer=optionsHelper)
optionsBox = optionsSizer.GetStaticBox()

# Translators: The label of a checkbox option in the Install NVDA dialog.
startOnLogonText = _("Use NVDA during sign-in")
self.startOnLogonCheckbox = optionsSizer.addItem(wx.CheckBox(self, label=startOnLogonText))
self.startOnLogonCheckbox = optionsSizer.addItem(wx.CheckBox(optionsBox, label=startOnLogonText))
self.bindHelpEvent("StartAtWindowsLogon", self.startOnLogonCheckbox)
if globalVars.appArgs.enableStartOnLogon is not None:
self.startOnLogonCheckbox.Value = globalVars.appArgs.enableStartOnLogon
Expand All @@ -197,19 +194,22 @@ def __init__(self, parent, isUpdate):
if self.isUpdate and shortcutIsPrevInstalled:
# Translators: The label of a checkbox option in the Install NVDA dialog.
keepShortCutText = _("&Keep existing desktop shortcut")
self.createDesktopShortcutCheckbox = optionsSizer.addItem(wx.CheckBox(self, label=keepShortCutText))
keepShortCutBox = wx.CheckBox(optionsBox, label=keepShortCutText)
self.createDesktopShortcutCheckbox = optionsSizer.addItem(keepShortCutBox)
else:
# Translators: The label of the option to create a desktop shortcut in the Install NVDA dialog.
# If the shortcut key has been changed for this locale,
# this change must also be reflected here.
createShortcutText = _("Create &desktop icon and shortcut key (control+alt+n)")
self.createDesktopShortcutCheckbox = optionsSizer.addItem(wx.CheckBox(self, label=createShortcutText))
createShortcutBox = wx.CheckBox(optionsBox, label=createShortcutText)
self.createDesktopShortcutCheckbox = optionsSizer.addItem(createShortcutBox)
self.bindHelpEvent("CreateDesktopShortcut", self.createDesktopShortcutCheckbox)
self.createDesktopShortcutCheckbox.Value = shortcutIsPrevInstalled if self.isUpdate else True

# Translators: The label of a checkbox option in the Install NVDA dialog.
createPortableText = _("Copy &portable configuration to current user account")
self.copyPortableConfigCheckbox = optionsSizer.addItem(wx.CheckBox(self, label=createPortableText))
createPortableBox = wx.CheckBox(optionsBox, label=createPortableText)
self.copyPortableConfigCheckbox = optionsSizer.addItem(createPortableBox)
self.bindHelpEvent("CopyPortableConfigurationToCurrentUserAccount", self.copyPortableConfigCheckbox)
self.copyPortableConfigCheckbox.Value = bool(globalVars.appArgs.copyPortableConfig)
if globalVars.appArgs.copyPortableConfig is None:
Expand All @@ -220,12 +220,12 @@ def __init__(self, parent, isUpdate):
bHelper = sHelper.addDialogDismissButtons(guiHelper.ButtonHelper(wx.HORIZONTAL))
if shouldAskAboutAddons:
# Translators: The label of a button to launch the add-on compatibility review dialog.
reviewAddonButton = bHelper.addButton(self, label=_("&Review add-ons..."))
reviewAddonButton = bHelper.addButton(optionsBox, label=_("&Review add-ons..."))
self.bindHelpEvent("InstallWithIncompatibleAddons", reviewAddonButton)
reviewAddonButton.Bind(wx.EVT_BUTTON, self.onReviewAddons)

# Translators: The label of a button to continue with the operation.
continueButton = bHelper.addButton(self, label=_("&Continue"), id=wx.ID_OK)
continueButton = bHelper.addButton(optionsBox, label=_("&Continue"), id=wx.ID_OK)
continueButton.SetDefault()
continueButton.Bind(wx.EVT_BUTTON, self.onInstall)
if shouldAskAboutAddons:
Expand All @@ -235,7 +235,7 @@ def __init__(self, parent, isUpdate):
)
continueButton.Enable(False)

bHelper.addButton(self, id=wx.ID_CANCEL)
bHelper.addButton(optionsBox, id=wx.ID_CANCEL)
# If we bind this using button.Bind, it fails to trigger when the dialog is closed.
self.Bind(wx.EVT_BUTTON, self.onCancel, id=wx.ID_CANCEL)

Expand Down Expand Up @@ -349,35 +349,38 @@ def __init__(self, parent):
# Translators: The label of a grouping containing controls to select the destination directory
# in the Create Portable NVDA dialog.
directoryGroupText = _("Portable &directory:")
groupHelper = sHelper.addItem(gui.guiHelper.BoxSizerHelper(self, sizer=wx.StaticBoxSizer(wx.StaticBox(self, label=directoryGroupText), wx.VERTICAL)))
groupSizer = wx.StaticBoxSizer(wx.VERTICAL, self, label=directoryGroupText)
groupHelper = sHelper.addItem(gui.guiHelper.BoxSizerHelper(self, sizer=groupSizer))
groupBox = groupSizer.GetStaticBox()
# Translators: The label of a button to browse for a directory.
browseText = _("Browse...")
# Translators: The title of the dialog presented when browsing for the
# destination directory when creating a portable copy of NVDA.
dirDialogTitle = _("Select portable directory")
directoryEntryControl = groupHelper.addItem(gui.guiHelper.PathSelectionHelper(self, browseText, dirDialogTitle))
directoryPathHelper = gui.guiHelper.PathSelectionHelper(groupBox, browseText, dirDialogTitle)
directoryEntryControl = groupHelper.addItem(directoryPathHelper)
self.portableDirectoryEdit = directoryEntryControl.pathControl
if globalVars.appArgs.portablePath:
self.portableDirectoryEdit.Value = globalVars.appArgs.portablePath

# Translators: The label of a checkbox option in the Create Portable NVDA dialog.
copyConfText = _("Copy current &user configuration")
self.copyUserConfigCheckbox = sHelper.addItem(wx.CheckBox(self, label=copyConfText))
self.copyUserConfigCheckbox = sHelper.addItem(wx.CheckBox(groupBox, label=copyConfText))
self.copyUserConfigCheckbox.Value = False
if globalVars.appArgs.launcher:
self.copyUserConfigCheckbox.Disable()
# Translators: The label of a checkbox option in the Create Portable NVDA dialog.
startAfterCreateText = _("&Start the new portable copy after creation")
self.startAfterCreateCheckbox = sHelper.addItem(wx.CheckBox(self, label=startAfterCreateText))
self.startAfterCreateCheckbox = sHelper.addItem(wx.CheckBox(groupBox, label=startAfterCreateText))
self.startAfterCreateCheckbox.Value = False

bHelper = sHelper.addDialogDismissButtons(gui.guiHelper.ButtonHelper(wx.HORIZONTAL), separated=True)

continueButton = bHelper.addButton(self, label=_("&Continue"), id=wx.ID_OK)
continueButton = bHelper.addButton(groupBox, label=_("&Continue"), id=wx.ID_OK)
continueButton.SetDefault()
continueButton.Bind(wx.EVT_BUTTON, self.onCreatePortable)

bHelper.addButton(self, id=wx.ID_CANCEL)
bHelper.addButton(groupBox, id=wx.ID_CANCEL)
# If we bind this using button.Bind, it fails to trigger when the dialog is closed.
self.Bind(wx.EVT_BUTTON, self.onCancel, id=wx.ID_CANCEL)

Expand Down
Loading