Skip to content

Commit

Permalink
Updated wxPython to 3.0.2.0.
Browse files Browse the repository at this point in the history
  • Loading branch information
jcsteh committed Jan 9, 2015
2 parents e7c90a3 + 0bc1955 commit 40a6012
Show file tree
Hide file tree
Showing 11 changed files with 156 additions and 21 deletions.
2 changes: 1 addition & 1 deletion miscDeps
Submodule miscDeps updated from eee656 to 703676
2 changes: 1 addition & 1 deletion readme.txt
Expand Up @@ -22,7 +22,7 @@ If you aren't sure, run git submodule update after every git pull, merge or chec

The following dependencies are included in Git submodules:
* comtypes, version 0.6.2: http://sourceforge.net/projects/comtypes/
* wxPython, version 2.8.12.1 unicode: http://www.wxpython.org/
* wxPython, version 3.0.2.0: http://www.wxpython.org/
* Python Windows Extensions, build 218: http://sourceforge.net/projects/pywin32/
* eSpeak, version 1.48.03: http://espeak.sourceforge.net/
* IAccessible2, version 1.3: http://www.linuxfoundation.org/collaborate/workgroups/accessibility/iaccessible2
Expand Down
35 changes: 26 additions & 9 deletions source/core.py
Expand Up @@ -70,6 +70,7 @@ def restart(disableAddons=False):
wx.GetApp().ExitMainLoop()
return
import subprocess
import winUser
import shellapi
options=[]
try:
Expand All @@ -85,7 +86,9 @@ def restart(disableAddons=False):
shellapi.ShellExecute(None, None,
sys.executable.decode("mbcs"),
subprocess.list2cmdline(sys.argv + options).decode("mbcs"),
None, 0)
None,
# #4475: ensure that the first window of the new process is not hidden by providing SW_SHOWNORMAL
winUser.SW_SHOWNORMAL)

def resetConfiguration(factoryDefaults=False):
"""Loads the configuration, installs the correct language support and initialises audio so that it will use the configured synth and speech settings.
Expand Down Expand Up @@ -228,19 +231,32 @@ def onEndSession(evt):
log.debug("Initializing GUI")
import gui
gui.initialize()

# #3763: In wxPython 3, the class name of frame windows changed from wxWindowClassNR to wxWindowNR.
# NVDA uses the main frame to check for and quit another instance of NVDA.
# To remain compatible with older versions of NVDA, create our own wxWindowClassNR.
# We don't need to do anything else because wx handles WM_QUIT for all windows.
import windowUtils
class MessageWindow(windowUtils.CustomWindow):
className = u"wxWindowClassNR"
messageWindow = MessageWindow(unicode(versionInfo.name))

# initialize wxpython localization support
locale = wx.Locale()
lang=languageHandler.getLanguage()
if '_' in lang:
wxLang=lang.split('_')[0]
else:
wxLang=lang
wxLang=locale.FindLanguageInfo(lang)
if not wxLang and '_' in lang:
wxLang=locale.FindLanguageInfo(lang.split('_')[0])
if hasattr(sys,'frozen'):
locale.AddCatalogLookupPathPrefix(os.path.join(os.getcwdu(),"locale"))
try:
locale.Init(lang,wxLang)
except:
pass
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)

import api
import winUser
import NVDAObjects.window
Expand Down Expand Up @@ -357,6 +373,7 @@ def Notify(self):
app.MainLoop()

log.info("Exiting")
messageWindow.destroy()
if updateCheck:
_terminate(updateCheck)

Expand Down
22 changes: 19 additions & 3 deletions source/gui/__init__.py
Expand Up @@ -478,12 +478,24 @@ def onActivate(self, evt):

def initialize():
global mainFrame
if mainFrame:
raise RuntimeError("GUI already initialized")
mainFrame = MainFrame()
wx.GetApp().SetTopWindow(mainFrame)

def terminate():
global mainFrame
mainFrame.Destroy()
# This is called after the main loop exits because WM_QUIT exits the main loop
# without destroying all objects correctly and we need to support WM_QUIT.
# Therefore, any request to exit should exit the main loop.
wx.CallAfter(mainFrame.Destroy)
# #4460: We need another iteration of the main loop
# so that everything (especially the TaskBarIcon) is cleaned up properly.
# ProcessPendingEvents doesn't seem to work, but MainLoop does.
# Because the top window gets destroyed,
# MainLoop thankfully returns pretty quickly.
wx.GetApp().MainLoop()
mainFrame = None

def showGui():
wx.CallAfter(mainFrame.showGui)
Expand Down Expand Up @@ -590,8 +602,8 @@ def onOk(self, evt):
try:
config.conf.save()
except:
pass
self.Close()
log.debugWarning("could not save",exc_info=True)
self.EndModal(wx.ID_OK)

@classmethod
def run(cls):
Expand Down Expand Up @@ -795,6 +807,10 @@ def Pulse(self):
# Translators: Announced periodically to indicate progress for an indeterminate progress bar.
speech.speakMessage(_("Please wait"))

def IsActive(self):
#4714: In wxPython 3, ProgressDialog.IsActive always seems to return False.
return winUser.isDescendantWindow(winUser.getForegroundWindow(), self.Handle)

def done(self):
self.timer.Stop()
if self.IsActive():
Expand Down
5 changes: 4 additions & 1 deletion source/gui/addonGui.py
Expand Up @@ -157,7 +157,10 @@ def installAddon(self, addonPath, closeAfter=False):
del progressDialog
finally:
if closeAfter:
self.onClose(None)
# #4460: If we do this immediately, wx seems to drop the WM_QUIT sent if the user chooses to restart.
# This seems to have something to do with the wx.ProgressDialog.
# The CallLater seems to work around this.
wx.CallLater(1, self.Close)

def OnRemoveClick(self,evt):
index=self.addonsList.GetFirstSelected()
Expand Down
3 changes: 2 additions & 1 deletion source/gui/installerGui.py
Expand Up @@ -65,10 +65,11 @@ def doInstall(createDesktopShortcut,startOnLogon,copyPortableConfig,isUpdate,sil
gui.messageBox(msg+_("Please press OK to start the installed copy."),
# Translators: The title of a dialog presented to indicate a successful operation.
_("Success"))
# #4475: ensure that the first window of the new process is not hidden by providing SW_SHOWNORMAL
shellapi.ShellExecute(None, None,
os.path.join(installer.defaultInstallPath,'nvda.exe'),
u"-r",
None, 0)
None, winUser.SW_SHOWNORMAL)

def doSilentInstall():
prevInstall=installer.isPreviousInstall()
Expand Down
6 changes: 4 additions & 2 deletions source/gui/logViewer.py
Expand Up @@ -20,7 +20,7 @@ def __init__(self, parent):
self.Bind(wx.EVT_CLOSE, self.onClose)
mainSizer = wx.BoxSizer(wx.VERTICAL)
self.outputCtrl = wx.TextCtrl(self, wx.ID_ANY, size=(500, 500), style=wx.TE_MULTILINE | wx.TE_READONLY|wx.TE_RICH)
self.outputCtrl.Bind(wx.EVT_CHAR, self.onOutputChar)
self.outputCtrl.Bind(wx.EVT_KEY_DOWN, self.onOutputKeyDown)
mainSizer.Add(self.outputCtrl, proportion=1, flag=wx.EXPAND)
self.SetSizer(mainSizer)
mainSizer.Fit(self)
Expand Down Expand Up @@ -78,10 +78,12 @@ def onSaveAsCommand(self, evt):
# Translators: Dialog text presented when NVDA cannot save a log file.
gui.messageBox(_("Error saving log: %s") % e.strerror, _("Error"), style=wx.OK | wx.ICON_ERROR, parent=self)

def onOutputChar(self, evt):
def onOutputKeyDown(self, evt):
key = evt.GetKeyCode()
# #3763: WX 3 no longer passes escape via evt_char in richEdit controls. Therefore evt_key_down must be used.
if key == wx.WXK_ESCAPE:
self.Close()
return
evt.Skip()

def activate():
Expand Down
11 changes: 9 additions & 2 deletions source/pythonConsole.py
Expand Up @@ -167,6 +167,7 @@ def __init__(self, parent):
self.Bind(wx.EVT_CLOSE, self.onClose)
mainSizer = wx.BoxSizer(wx.VERTICAL)
self.outputCtrl = wx.TextCtrl(self, wx.ID_ANY, size=(500, 500), style=wx.TE_MULTILINE | wx.TE_READONLY|wx.TE_RICH)
self.outputCtrl.Bind(wx.EVT_KEY_DOWN, self.onOutputKeyDown)
self.outputCtrl.Bind(wx.EVT_CHAR, self.onOutputChar)
mainSizer.Add(self.outputCtrl, proportion=2, flag=wx.EXPAND)
inputSizer = wx.BoxSizer(wx.HORIZONTAL)
Expand Down Expand Up @@ -333,13 +334,19 @@ def onInputChar(self, evt):
return
evt.Skip()

def onOutputKeyDown(self, evt):
key = evt.GetKeyCode()
# #3763: WX 3 no longer passes escape to evt_char for richEdit fields, therefore evt_key_down is used.
if key == wx.WXK_ESCAPE:
self.Close()
return
evt.Skip()

def onOutputChar(self, evt):
key = evt.GetKeyCode()
if key == wx.WXK_F6:
self.inputCtrl.SetFocus()
return
elif key == wx.WXK_ESCAPE:
self.Close()
evt.Skip()

def initialize():
Expand Down
4 changes: 3 additions & 1 deletion source/updateCheck.py
Expand Up @@ -28,6 +28,7 @@
from logHandler import log
import config
import shellapi
import winUser

#: The URL to use for update checks.
CHECK_URL = "http://www.nvda-project.org/updateCheck"
Expand Down Expand Up @@ -369,10 +370,11 @@ def _downloadSuccess(self):
_("Install Update"))
state["removeFile"] = self.destPath
saveState()
# #4475: ensure that the new process shows its first window, by providing SW_SHOWNORMAL
shellapi.ShellExecute(None, None,
self.destPath.decode("mbcs"),
u"--install -m",
None, 0)
None, winUser.SW_SHOWNORMAL)

class DonateRequestDialog(wx.Dialog):
# Translators: The message requesting donations from users.
Expand Down
2 changes: 2 additions & 0 deletions source/versionInfo.py
Expand Up @@ -27,6 +27,8 @@ def _updateVersionFromVCS():
except:
pass

# ticket:3763#comment:19: name must be str, not unicode.
# Otherwise, py2exe will break.
name="NVDA"
longName=_("NonVisual Desktop Access")
version="2015.1dev"
Expand Down
85 changes: 85 additions & 0 deletions source/windowUtils.py
Expand Up @@ -8,7 +8,10 @@
"""

import ctypes
import weakref
import winUser
from winUser import WNDCLASSEXW, WNDPROC, LRESULT
from logHandler import log

WNDENUMPROC = ctypes.WINFUNCTYPE(ctypes.wintypes.BOOL, ctypes.wintypes.HWND, ctypes.wintypes.LPARAM)
def findDescendantWindow(parent, visible=None, controlID=None, className=None):
Expand Down Expand Up @@ -90,3 +93,85 @@ def physicalToLogicalPoint(window, x, y):
point = ctypes.wintypes.POINT(x, y)
_physicalToLogicalPoint(window, ctypes.byref(point))
return point.x, point.y

appInstance = ctypes.windll.kernel32.GetModuleHandleW(None)
class CustomWindow(object):
"""Base class to enable simple implementation of custom windows.
Subclasses need only set L{className} and implement L{windowProc}.
Simply create an instance to create the window.
The window will be destroyed when the instance is deleted,
but it can be explicitly destroyed using L{destroy}.
"""

#: The class name of this window.
#: @type: unicode
className = None

_hwndsToInstances = weakref.WeakValueDictionary()

def __init__(self, windowName=None):
"""Constructor.
@raise WindowsError: If an error occurs.
"""
if not isinstance(self.className, unicode):
raise ValueError("className attribute must be a unicode string")
if windowName and not isinstance(windowName, unicode):
raise ValueError("windowName must be a unicode string")
self._wClass = WNDCLASSEXW(
cbSize=ctypes.sizeof(WNDCLASSEXW),
lpfnWndProc = CustomWindow._rawWindowProc,
hInstance = appInstance,
lpszClassName = self.className,
)
res = self._classAtom = ctypes.windll.user32.RegisterClassExW(ctypes.byref(self._wClass))
if res == 0:
raise ctypes.WinError()
res = ctypes.windll.user32.CreateWindowExW(0, self._classAtom, windowName or self.className, 0, 0, 0, 0, 0, None, None, appInstance, None)
if res == 0:
raise ctypes.WinError()
#: The handle to the created window.
#: @type: int
self.handle = res
self._hwndsToInstances[res] = self

def destroy(self):
"""Destroy the window.
This will be called automatically when this instance is deleted,
but you may wish to call it earlier.
"""
ctypes.windll.user32.DestroyWindow(self.handle)
self.handle = None
ctypes.windll.user32.UnregisterClassW(self._classAtom, appInstance)

def __del__(self):
if self.handle:
self.destroy()

def windowProc(self, hwnd, msg, wParam, lParam):
"""Process messages sent to this window.
@param hwnd: The handle to this window.
@type hwnd: int
@param msg: The message.
@type msg: int
@param wParam: Additional message information.
@type wParam: int
@param lParam: Additional message information.
@type lParam: int
@return: The result of the message processing
or C{None} to call DefWindowProc.
@rtype: int or None
"""

@WNDPROC
def _rawWindowProc(hwnd, msg, wParam, lParam):
try:
inst = CustomWindow._hwndsToInstances[hwnd]
except KeyError:
return ctypes.windll.user32.DefWindowProcW(hwnd, msg, wParam, lParam)
try:
res = inst.windowProc(hwnd, msg, wParam, lParam)
if res is not None:
return res
except:
log.exception("Error in wndProc")
return ctypes.windll.user32.DefWindowProcW(hwnd, msg, wParam, lParam)

0 comments on commit 40a6012

Please sign in to comment.