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
43 changes: 23 additions & 20 deletions source/core.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# -*- 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
import wx

"""NVDA core"""

RPC_E_CALL_CANCELED = -2147418110
Expand Down Expand Up @@ -85,7 +87,6 @@ def doStartupDialogs():
gui.mainFrame.onToggleSpeechViewerCommand(evt=None)
import inputCore
if inputCore.manager.userGestureMap.lastUpdateContainedError:
import wx
gui.messageBox(_("Your gesture map file contains errors.\n"
"More details about the errors can be found in the log file."),
_("gesture map File Error"), wx.OK|wx.ICON_EXCLAMATION)
Expand All @@ -97,7 +98,6 @@ def doStartupDialogs():
if updateCheck and not config.conf['update']['askedAllowUsageStats']:
# a callback to save config after the usage stats question dialog has been answered.
def onResult(ID):
import wx
if ID in (wx.ID_YES,wx.ID_NO):
try:
config.conf.save()
Expand All @@ -109,7 +109,6 @@ def onResult(ID):
def restart(disableAddons=False, debugLogging=False):
"""Restarts NVDA by starting a new copy."""
if globalVars.appArgs.launcher:
import wx
globalVars.exitCode=3
wx.GetApp().ExitMainLoop()
return
Expand Down Expand Up @@ -207,6 +206,24 @@ def _setInitialFocus():
except:
log.exception("Error retrieving initial focus")


def getWxLangOrNone(lang: str) -> Optional[wx.LanguageInfo]:
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 @@ -284,7 +301,6 @@ def main():
log.debugWarning("Slow starting core (%.2f sec)" % (time.time()-globalVars.startTime))
# Translators: This is spoken when NVDA is starting.
speech.speakMessage(_("Loading NVDA. Please wait..."))
import wx
# wxPython 4 no longer has either of these constants (despite the documentation saying so), some add-ons may rely on
# them so we add it back into wx. https://wxpython.org/Phoenix/docs/html/wx.Window.html#wx.Window.Centre
wx.CENTER_ON_SCREEN = wx.CENTRE_ON_SCREEN = 0x2
Expand Down Expand Up @@ -419,25 +435,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(lang)
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 Expand Up @@ -654,7 +659,6 @@ def requestPump():
return
# This isn't the main thread. wx timers cannot be run outside the main thread.
# Therefore, Have wx start it in the main thread with a CallAfter.
import wx
wx.CallAfter(_pump.Start,PUMP_MAX_DELAY, True)

def callLater(delay, callable, *args, **kwargs):
Expand All @@ -663,7 +667,6 @@ def callLater(delay, callable, *args, **kwargs):
This function should never be used to execute code that brings up a modal UI as it will cause NVDA's core to block.
This function can be safely called from any thread.
"""
import wx
if threading.get_ident() == mainThreadId:
return wx.CallLater(delay, _callLaterExec, callable, args, kwargs)
else:
Expand Down
24 changes: 12 additions & 12 deletions source/gui/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,8 @@ def initialize():
if mainFrame:
raise RuntimeError("GUI already initialized")
mainFrame = MainFrame()
wxLang = core.getWxLang(languageHandler.getLanguage())
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 +727,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 +745,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 +809,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

Loading