## Copyright (c) 2003 Henk Punt
## Permission is hereby granted, free of charge, to any person obtaining
## a copy of this software and associated documentation files (the
## "Software"), to deal in the Software without restriction, including
## without limitation the rights to use, copy, modify, merge, publish,
## distribute, sublicense, and/or sell copies of the Software, and to
## permit persons to whom the Software is furnished to do so, subject to
## the following conditions:
## The above copyright notice and this permission notice shall be
## included in all copies or substantial portions of the Software.
## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
## EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
## MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
## NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
## LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
## OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
## WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
from windows import *
from ctypes import *
import sys
import weakref
quit = False
class HandleMap(dict):
"""a special weakreference map for mapping window handles to python instances
when a python instance becomes garbage, the __dispose__ method of HandleMap
is called, deleting the handle from the map and freeing OS resources by calling
the method stored in the __dispose__ variable of the garbage python instance.
This latter method should be bound to a windows-free-routine corresponding to the
type of the handle"""
def __setitem__(self, handle, value):
# watch the lambda closure, freezing the binding of:
# - fndisp to the __dispose__ variable of the value object
# - handle to the provided windows-handle in the first actual parameter
lmdisp = lambda wr, fndisp = value.__dispose__, dbgstr = str(value.__class__): \
self.__dispose__(handle, wr, fndisp, dbgstr)
dict.__setitem__(self, handle, weakref.ref(value, lmdisp))
def __getitem__(self, handle):
return dict.__getitem__(self, handle)() # weak refs are 'called' to return the referred object
def get(self, k, d = None):
if self.has_key(k):
return self[k]
else:
return d
def __dispose__(self, handle, wr, fndisp, dbgstr): # callback of weakref wr, called when wr() is garbage
self.__delitem__(handle)
if not quit:
fndisp(handle)
hndlMap = HandleMap() #contains the mapping from python instances (of Window) to windows HANDLES
createHndlMap = {} #used while handling messages during CreateWindow(Ex)
def globalWndProc(hWnd, nMsg, wParam, lParam):
"""The purpose of globalWndProc is route messages coming in on the global queue
to the appropriate python Window instance for handling.
Also it establishes the mapping from python instances to window HANDLES by processing the WM_NCCREATE message
"""
#print hWnd, nMsg, wParam, lParam
#print hndlMap
try:
if nMsg == WM_NCCREATE:
#a venster window is being creaated trough CreateWindowEx,
#establish the mapping between the windows HANDLE and the Window instance
#the window instance is to be found in the createHndlMap by looking up
#the key that was given as a parameter to CreateWindowEx
createStruct = CREATESTRUCT.from_address(int(lParam))
window = createHndlMap.get(int(createStruct.lpCreateParams), None)
if window:
#it is a venster window being created, establish the mapping:
WindowsObject.__init__(window, hWnd)
handled = False
result = None
window = hndlMap.get(hWnd, None)
if window:
#this is a known venster window, let the window process its own msgs
handled, result = window.WndProc(hWnd, nMsg, wParam, lParam)
if not handled and window._issubclassed_:
#its a subclassed window, try old window proc
result = CallWindowProc(window._old_wnd_proc_, hWnd, nMsg, wParam, lParam)
handled = True #always handled, either by old window proc, or old window proc called default handling
if not handled:
#still not handled, perform default processing
return DefWindowProc(hWnd, nMsg, wParam, lParam) #windows default processing
else:
return result
except:
try:
import traceback
traceback.print_exc()
except:
pass #this happens when the python runtime is already exitting, but we are still registered
#as a window proc and windows keeps calling the callback
cGlobalWndProc = WNDPROC(globalWndProc)
def handle(obj):
if not obj:
return NULL
elif hasattr(obj, 'handle'):
return obj.handle
else:
return obj
def instanceFromHandle(handle):
return hndlMap.get(handle, None)
def instanceOrHandle(handle):
return hndlMap.get(handle, handle)
def windowFromHandle(handle):
"""returns None if handle = 0, the python Window instance if
handle is known, or a new pseudo window if handle != 0 and not known"""
if not handle: return None
window = hndlMap.get(handle, None)
if not window:
window = Window(hWnd = handle)
return window
class WindowsObject(object):
def __init__(self, handle, managed = True):
"""managed objects are stored in a global map so that they can
be looked up by their handle, also this allows for calling the
appropriate destructor function (__dispose__) whenever the object
becomes garbage"""
self.m_handle = handle
if managed: hndlMap[handle] = self
handle = property(lambda self: self.m_handle)
def __str__(self):
return '<%s handle: %d>' % (self.__class__.__name__, self.handle)
def __equals__(self, other):
return self.handle == other.handle
class Event(object):
def __init__(self, hWnd, nMsg, wParam, lParam):
self.hWnd = hWnd
self.nMsg = nMsg
self.lParam = lParam
self.wParam = wParam
self.handled = 0
def structure(self, nmStructure):
return nmStructure.from_address(int(self.lParam))
def __str__(self):
return "<event hWnd: %d, nMsg: %d, lParam: %d, wParam: %d>" % (self.hWnd, self.nMsg,
self.lParam, self.wParam)
class MSG_MAP(list):
def __init__(self, entries):
list.__init__(self, entries)
self._msg_map_ = {}
for entry in self:
self.append(entry)
def append(self, entry):
entry.__install__(self)
def Handle(self, receiver, hWnd, nMsg, wParam, lParam, clazz):
handler = self._msg_map_.get(nMsg, None)
if handler:
event = Event(hWnd, nMsg, wParam, lParam)
event.handled = True #the presence of a handler means that by default we assume the event to be handled
#if the handler wants to force further processing by parent class map
#the handler will set event.handled to False
result = handler(receiver, event)
if event.handled:
if result == None:
return (True, NULL)
else:
return (True, int(result))
return (False, NULL)
def HandleBaseClasses(self, receiver, hWnd, nMsg, wParam, lParam, clazz):
for baseClass in clazz.__bases__:
if issubclass(baseClass, Window):
handled, result = baseClass._msg_map_.Dispatch(receiver, hWnd, nMsg, wParam, lParam, baseClass)
if handled:
return (True, result)
return (False, NULL)
def Dispatch(self, receiver, hWnd, nMsg, wParam, lParam, clazz = None):
clazz = clazz or receiver.__class__
handled, result = self.Handle(receiver, hWnd, nMsg, wParam, lParam, clazz)
if handled:
return (True, result)
handled, result = self.HandleBaseClasses(receiver, hWnd, nMsg, wParam, lParam, clazz)
if handled:
return (True, result)
#nobody handled msg
return (False, NULL)
def DispatchMSG(self, receiver, msg):
return self.Dispatch(receiver, msg.hWnd, msg.message, msg.wParam, msg.lParam)
def __str__(self):
return str(self._msg_map_)
class WindowType(type):
def __init__(cls, name, bases, dct):
#make sure every window class has its own msg map
if not dct.has_key('_msg_map_'):
cls._msg_map_ = MSG_MAP([])
super(WindowType, cls).__init__(name, bases, dct)
#see if decorators were used to map events to handlers,
#and install the handlers in the msgmap
for item in dct.values():
if hasattr(item, 'handler'):
cls._msg_map_.append(item.handler)
hInstance = GetModuleHandle(NULL)
wndClasses = []
RCDEFAULT = RECT(top = CW_USEDEFAULT, left = CW_USEDEFAULT, right = 0, bottom = 0)
class Window(WindowsObject):
__metaclass__ = WindowType
_window_class_ = ''
_window_title_ = ''
_window_style_ = WS_OVERLAPPEDWINDOW | WS_VISIBLE
_window_style_ex_ = 0
_window_icon_ = LoadIcon(NULL, IDI_APPLICATION)
_window_icon_sm_ = LoadIcon(NULL, IDI_APPLICATION)
_window_background_ = 0
_window_class_style_ = 0
_window_style_clip_children_and_siblings_ = True
_window_dbg_msg_ = False
_window_width_ = CW_USEDEFAULT
_window_height_ = CW_USEDEFAULT
__dispose__ = DestroyWindow
def __init__(self, title = "",
style = None,
exStyle = None,
parent = None,
menu = None,
rcPos = RCDEFAULT,
orStyle = None,
orExStyle = None,
nandStyle = None,
nandExStyle = None,
width = CW_USEDEFAULT,
height = CW_USEDEFAULT,
hWnd = None):
if hWnd: #wrapping instead of creating
self.m_handle = hWnd #note client is responsible for deleting
return
windowClassExists = False
cls = WNDCLASSEX()
if self._window_class_:
if GetClassInfo(hInstance, self._window_class_, byref(cls)):
windowClassExists = True
#determine whether we are going to subclass an existing window class
#or create a new windowclass
self._issubclassed_ = self._window_class_ and windowClassExists
if not self._issubclassed_:
#if no _window_class_ is given, generate a new one
className = self._window_class_ or "venster_wtl_%s" % str(id(self.__class__))
cls = WNDCLASSEX()
cls.cbSize = sizeof(cls)
cls.lpszClassName = className
cls.hInstance = hInstance
cls.lpfnWndProc = cGlobalWndProc
cls.style = self._window_class_style_
cls.hbrBackground = self._window_background_
cls.hIcon = handle(self._window_icon_)
cls.hIconSm = handle(self._window_icon_sm_)
cls.hCursor = LoadCursor(NULL, IDC_ARROW)
#cls structure needs to stay on heap
wndClasses.append(cls)
atom = RegisterClassEx(byref(cls))
else:
#subclass existing window class.
className = self._window_class_
title = title or self._window_title_
if style is None:
style = self._window_style_
if exStyle is None:
exStyle = self._window_style_ex_
if orStyle:
style |= orStyle
if orExStyle:
exStyle |= orExStyle
if self._window_style_clip_children_and_siblings_:
style |= WS_CLIPCHILDREN
style |= WS_CLIPSIBLINGS
if nandStyle:
style &= ~nandStyle
left, right = rcPos.left, rcPos.right
top, bottom = rcPos.top, rcPos.bottom
if width == CW_USEDEFAULT:
width = self._window_width_
if left == CW_USEDEFAULT and width != CW_USEDEFAULT:
right = CW_USEDEFAULT + width
if height == CW_USEDEFAULT:
height = self._window_height_
if top == CW_USEDEFAULT and height != CW_USEDEFAULT:
bottom = CW_USEDEFAULT + height
#for normal windows created trough venster, the mapping between window handle
#and window instance will be established by processing the WM_NCCREATE msg
#and looking up the instance in the createhndlMap
createHndlMap[id(self)] = self
hWnd = CreateWindowEx(exStyle,
className,
title,
style,
left,
top,
right - left,
bottom - top,
handle(parent),
handle(menu),
hInstance,
id(self))
del createHndlMap[id(self)]
## print """
## CreateWindowEx [exStyle = %d, className = %s,
## title = %s, style = %d, x = %d, y = %d,
## nWidth = %d, nHeight = %d, hwndParent = %d,
## hMenu = %d, hInstance = %d] -> hWnd = %d""" % (exStyle, className, title, style, rcPos.left,
## rcPos.top, nWidth, nHeight, handle(parent),
## handle(menu), hInstance, hWnd)
if self._issubclassed_:
#for subclassed windows, we establish the instance <-> handle mapping here
WindowsObject.__init__(self, hWnd)
self._old_wnd_proc_ = self.SubClass(cGlobalWndProc)
def SubClass(self, newWndProc):
return SetWindowLong(self.handle, GWL_WNDPROC, newWndProc)
class Interceptor(object):
def __init__(self, receiver, window, msg_map, nMsg = [WM_NOTIFY]):
self.nMsg = dict([(x, 1) for x in nMsg])
self.newProc = WNDPROC(self.WndProc)
self.oldProc = window.SubClass(self.newProc)
self._msg_map_ = msg_map
self.receiver = receiver
def dispose(self):
self.WndProc = lambda self, hWnd, nMsg, wParam, lParam: 0
del self.receiver
del self._msg_map_
del self.newProc
def WndProc(self, hWnd, nMsg, wParam, lParam):
if nMsg in self.nMsg and hasattr(self, 'receiver'):
handled, res = self._msg_map_.Dispatch(self.receiver, hWnd, nMsg, wParam, lParam)
else:
handled = 0
if not handled:
return CallWindowProc(self.oldProc, hWnd, nMsg, wParam, lParam)
else:
return res
def Intercept(self, receiver, msgMap, nMsg = [WM_NOTIFY]):
return Window.Interceptor(self, receiver, msgMap, nMsg = nMsg)
def InterceptParent(self, nMsg = [WM_NOTIFY]):
"""intercepts msg proc in order to reroute msgs to self"""
self._interceptParent = self.Intercept(self.GetParent(), self._msg_map_, nMsg = nMsg)
def dispose(self):
if hasattr(self, '_interceptParent'):
self._interceptParent.dispose()
del self._interceptParent
def WndProc(self, hWnd, nMsg, wParam, lParam):
if self._window_dbg_msg_: print self, hWnd, nMsg, wParam, lParam
return self._msg_map_.Dispatch(self, hWnd, nMsg, wParam, lParam)
def IsDialogMessage(self, lpmsg):
return IsDialogMessage(self.handle, lpmsg)
def PreTranslateMessage(self, msg):
return 0
def TranslateAccelerator(self, msg):
return 0
def __repr__(self):
return '<Window hWnd: %d>' % self.handle
#this is the base class for all handlers defined in msg maps
class HANDLER(object):
#the handler is given in the msg map as a unbound (static) method
#on some class X, to enable a derived class to override a handler method
#of a parent class Y, a lambda trick is needed to pick the correct handler
#(that of the base class)
def __init__(self, handler):
#TODO how to determine if handler is a lambda or a named function without
#looking at '__name__'?:
if not handler:
self.m_handler = None
elif handler.__name__ == '<lambda>':
self.m_handler = handler
else:
#trick to make handler 'virtual' again
self.m_handler = lambda self, event: getattr(self, handler.__name__)(event)
def __call__(self, receiver, event):
return self.handler(receiver, event)
handler = property(lambda self: self.m_handler)
#Handler for normal window messages (e.g. WM_SIZE, WM_CLOSE, WM_PAINT etc)
class MSG_HANDLER(HANDLER):
def __init__(self, msg, handler):
HANDLER.__init__(self, handler)
self.msg = msg
def __install__(self, msgMap):
msgMap._msg_map_[self.msg] = self
class NTF_MAP(dict):
def __call__(self, receiver, event):
nmhdr = NMHDR.from_address(int(event.lParam))
handler = self.get(str(nmhdr.code), None)
if handler:
event.nmhdr = nmhdr
return handler(receiver, event)
else:
event.handled = 0
return 0
#handler for notification messages
#handles all notifications with the given code
class NTF_HANDLER(HANDLER):
def __init__(self, code, handler):
HANDLER.__init__(self, handler)
self.code = code
def __install__(self, msgMap):
notifMap = msgMap._msg_map_.setdefault(WM_NOTIFY, NTF_MAP())
notifMap[str(self.code)] = self
#support for WM_COMMAND msgs
#cmd is a map from id -> [(code, handler), ...]
#first handler in the list that matches the code is fired
#if code == -1, than handler is fired for any code
class CMD_MAP(dict):
def __call__(self, receiver, event):
code = HIWORD(event.wParam)
id = LOWORD(event.wParam)
for handlerCode, handler in self.get(id, []):
if handlerCode == -1 or handlerCode == code:
event.id = id
event.code = code
return handler(receiver, event)
#not handled
event.handled = 0
return 0
#maps command message based on control id AND notification code
class CMD_HANDLER(HANDLER):
def __init__(self, id, code, handler):
HANDLER.__init__(self, handler)
self.id, self.code = id, code
def __install__(self, msgMap):
cmdMap = msgMap._msg_map_.setdefault(WM_COMMAND, CMD_MAP())
notifList = cmdMap.setdefault(self.id, [])
notifList.append((self.code, self))
#maps command message based on control id
class CMD_ID_HANDLER(HANDLER):
def __init__(self, id, handler):
HANDLER.__init__(self, handler)
self.id = id
def __install__(self, msgMap):
cmdMap = msgMap._msg_map_.setdefault(WM_COMMAND, CMD_MAP())
notifList = cmdMap.setdefault(self.id, [])
notifList.append((-1, self))
#deprecated, will be removed before 1.0
class CHAIN_MSG_MAP (object):
def __init__(self, msgMap): pass
def __install__(self, msgMap): pass
#decorator versions of the above
def msg_handler(msg):
def decorator_func(func):
func.handler = MSG_HANDLER(msg, func)
return func
return decorator_func
def cmd_handler(id, code = None):
def decorator_func(func):
if code:
func.handler = CMD_HANDLER(id, code, func)
else:
func.handler = CMD_ID_HANDLER(id, func)
return func
return decorator_func
def ntf_handler(code):
def decorator_func(func):
func.handler = NTF_HANDLER(code, func)
return func
return decorator_func
#TODO allow the addition of more specific filters
#TODO make filters weak so that remove filter is not needed
class MessageLoop:
def __init__(self):
self.m_filters = {}
def AddFilter(self, filterFunc):
self.m_filters[filterFunc] = 1
def RemoveFilter(self, filterFunc):
del self.m_filters[filterFunc]
def Run(self):
msg = MSG()
lpmsg = byref(msg)
while GetMessage(lpmsg, 0, 0, 0):
if not self.PreTranslateMessage(msg):
TranslateMessage(lpmsg)
DispatchMessage(lpmsg)
global quit
quit = True
def PreTranslateMessage(self, msg):
for filter in self.m_filters.keys():
if filter(msg):
return 1
return 0
theMessageLoop = MessageLoop()
def GetMessageLoop():
return theMessageLoop
def Run():
theMessageLoop.Run()
#hWndMap should be empty at this point, container widgets
#should auto-dispose of their children! (somehow)
#print hndlMap
#import gc
#print gc.garbage
class Application(object):
def Run(self):
return Run()
def Quit(self, nExitCode = 0):
"""quits the application by posting the WM_QUIT message with the given
exitCode"""
PostQuitMessage(nExitCode)
|