Added ability to use xml resource files. Still need to add ability to

subclass wxXmlResourceHandler, etc...

Added wxGridAutoEditMixin to the mixins library package.

Made ColourSelect be derived from wxButton.

Fixed a few bugs here and there, added some missing methods, etc.


git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@10588 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Robin Dunn
2001-06-15 21:43:26 +00:00
parent dd2c8b7c33
commit d56cebe7a4
43 changed files with 4629 additions and 1854 deletions

View File

@@ -1,6 +1,9 @@
#
# This was modified from rpcMixin.py distributed with wxPython
#
#----------------------------------------------------------------------
# Name: rpcMixin
# Version: 0.1
# Version: 0.2.0
# Purpose: provides xmlrpc server functionality for wxPython
# applications via a mixin class
#
@@ -9,51 +12,74 @@
# (http://www.pythonware.com/products/xmlrpc/)
# the code was developed and tested using version 0.9.8
#
# Author: Greg Landrum (Landrum@RationalDiscovery.com)
# Author: greg Landrum (Landrum@RationalDiscovery.com)
#
# Copyright: (c) 2000 by Greg Landrum and Rational Discovery LLC
# Copyright: (c) 2000, 2001 by Greg Landrum and Rational Discovery LLC
# Licence: wxWindows license
#----------------------------------------------------------------------
"""
Some Notes:
"""provides xmlrpc server functionality for wxPython applications via a mixin class
1) The xmlrpc server runs in a separate thread from the main GUI
application, communication between the two threads using a custom
event (see the Threads demo in the wxPython docs for more info).
**Some Notes:**
2) Neither the server nor the client are particularly smart about
checking method names. So it's easy to shoot yourself in the foot
by calling improper methods. It would be pretty easy to add
either a list of allowed methods or a list of forbidden methods.
1) The xmlrpc server runs in a separate thread from the main GUI
application, communication between the two threads using a custom
event (see the Threads demo in the wxPython docs for more info).
3) Authentication of xmlrpc clients is *not* performed. I think it
would be pretty easy to do this in a hacky way, but I haven't done
it yet.
2) Neither the server nor the client are particularly smart about
checking method names. So it's easy to shoot yourself in the foot
by calling improper methods. It would be pretty easy to add
either a list of allowed methods or a list of forbidden methods.
4) The default port number is 800, it's a windows thing... at least
it seems like a windows thing to me. Since I'm not being smart
about port numbers, you can probably hork yourself arbitrarily by
firing up more than one xmlrpc-active frame at the same time, but
I haven't tried that.
3) Authentication of xmlrpc clients is *not* performed. I think it
would be pretty easy to do this in a hacky way, but I haven't done
it yet.
5) See the bottom of this file for an example of using the class.
4) See the bottom of this file for an example of using the class.
Obligatory disclaimer:
**Obligatory disclaimer:**
This is my first crack at both using xmlrpc and multi-threaded
programming, so there could be huge horrible bugs or design
flaws. If you see one, I'd love to hear about them.
"""
""" ChangeLog
23 May 2001: Version bumped to 0.2.0
Numerous code and design changes
21 Mar. 2001: Version bumped to 0.1.4
Updated rpcMixin.OnExternal to support methods with further references
(i.e. now you can do rpcClient.foo.bar() and have it work)
This probably ain't super legal in xmlrpc land, but it works just fine here
and we need it.
6 Mar. 2001: Version bumped to 0.1.3
Documentation changes to make this compatible with happydoc
21 Jan. 2001: Version bumped to 0.1.2
OnExternal() method in the mixin class now uses getattr() to check if
a desired method is present. It should have been done this way in
the first place.
14 Dec. 2000: Version bumped to 0.1.1
rearranged locking code and made other changes so that multiple
servers in one application are possible.
"""
from wxPython.wx import *
import xmlrpcserver
import Threading
import xmlrpcserver,xmlrpclib
import threading
import SocketServer
import string
import new
import sys
rpcPENDING = 0
rpcDONE = 1
rpcEXCEPT = 2
class RPCRequest:
"""A wrapper to use for handling requests and their responses"""
status = rpcPENDING
@@ -64,6 +90,7 @@ wxEVT_EXTERNAL_EVENT = 25015
class ExternalEvent(wxPyEvent):
"""The custom event class used to pass xmlrpc calls from
the server thread into the GUI thread
"""
def __init__(self,method,args):
wxPyEvent.__init__(self)
@@ -71,8 +98,15 @@ class ExternalEvent(wxPyEvent):
self.method = method
self.args = args
self.rpcStatus = RPCRequest()
self.rpcStatusLock = Threading.Lock()
self.rpcCondVar = Threading.Condition()
self.rpcStatusLock = threading.Lock()
self.rpcCondVar = threading.Condition()
def Destroy(self):
self.method=None
self.args=None
self.rpcStatus = None
self.rpcStatusLock = None
self.rpcondVar = None
def EVT_EXTERNAL_EVENT(win,func):
win.Connect(-1,-1,wxEVT_EXTERNAL_EVENT,func)
@@ -80,11 +114,28 @@ def EVT_EXTERNAL_EVENT(win,func):
class Handler(xmlrpcserver.RequestHandler):
"""The handler class that the xmlrpcserver actually calls
when a request comes in.
"""
def log_message(self,*args):
""" causes the server to stop spewing messages every time a request comes in
"""
pass
def call(self,method,params):
"""When an xmlrpc request comes in, this is the method that
gets called.
**Arguments**
- method: name of the method to be called
- params: arguments to that method
"""
if method == '_rpcPing':
# we just acknowledge these without processing them
return 'ack'
# construct the event
evt = ExternalEvent(method,params)
@@ -93,16 +144,16 @@ class Handler(xmlrpcserver.RequestHandler):
evt.rpcStatus.status = rpcPENDING
evt.rpcStatusLock.release()
# acquire the condition lock
evt.rpcCondVar.acquire()
# dispatch the event to the GUI
wxPostEvent(self._app,evt)
# wait for the GUI to finish
while evt.rpcStatus.status == rpcPENDING:
evt.rpcCondVar.wait()
evt.rpcCondVar.release()
evt.rpcStatusLock.acquire()
if evt.rpcStatus.stwxTheColourDatabaseatus == rpcEXCEPT:
if evt.rpcStatus.status == rpcEXCEPT:
# The GUI threw an exception, release the status lock
# and re-raise the exception
evt.rpcStatusLock.release()
@@ -110,9 +161,36 @@ class Handler(xmlrpcserver.RequestHandler):
else:
# everything went through without problems
s = evt.rpcStatus.result
evt.rpcStatusLock.release()
evt.Destroy()
self._app = None
return s
# this global Event is used to let the server thread
# know when it should quit
stopEvent = threading.Event()
stopEvent.clear()
class _ServerThread(threading.Thread):
""" this is the Thread class which actually runs the server
"""
def __init__(self,server,verbose=0):
self._xmlServ = server
threading.Thread.__init__(self,verbose=verbose)
def stop(self):
stopEvent.set()
def shouldStop(self):
return stopEvent.isSet()
def run(self):
while not self.shouldStop():
self._xmlServ.handle_request()
self._xmlServ = None
class rpcMixin:
"""A mixin class to provide xmlrpc server functionality to wxPython
frames/windows
@@ -122,68 +200,148 @@ class rpcMixin:
RPC is handled.
"""
def __init__(self,host='',port=800):
# we'll try a range of ports for the server, this is the size of the
# range to be scanned
nPortsToTry=20
if sys.platform == 'win32':
defPort = 800
else:
defPort = 8023
def __init__(self,host='',port=-1,verbose=0,portScan=1):
"""Constructor
**Arguments**
- host: (optional) the hostname for the server
- port: (optional) the port the server will use
- verbose: (optional) if set, the server thread will be launched
in verbose mode
- portScan: (optional) if set, we'll scan across a number of ports
to find one which is avaiable
"""
Arguments:
host: (optional) the hostname for the server
port: (optional) the port the server will use
"""
EVT_EXTERNAL_EVENT(swxTheColourDatabaseelf,self.OnExternal)
if port == -1:
port = self.defPort
self.verbose=verbose
EVT_EXTERNAL_EVENT(self,self.OnExternal)
if hasattr(self,'OnClose'):
self._origOnClose = self.OnClose
self.Disconnect(-1,-1,wxEVT_CLOSE_WINDOW)
else:
self._origOnClose = None
EVT_CLOSE(self,self.OnClose)
self.OnClose = self.RPCOnClose
EVT_CLOSE(self,self.RPCOnClose)
exec('class Handler%d(Handler): pass'%(port))
exec('tClass= Handler%d'%(port))
tClass = new.classobj('Handler%d'%(port),(Handler,),{})
tClass._app = self
self._xmlServ = SocketServer.TCPServer((host,port),tClass)
self.servThread = Threading.Thread(target=self._xmlServ.serve_forever)
self.servThread.setDaemon(1)
if portScan:
self.rpcPort = -1
for i in xrange(self.nPortsToTry):
try:
xmlServ = SocketServer.TCPServer((host,port+i),tClass)
except:
pass
else:
self.rpcPort = port+i
else:
self.rpcPort = port
try:
xmlServ = SocketServer.TCPServer((host,port),tClass)
except:
self.rpcPort = -1
if self.rpcPort == -1:
raise 'RPCMixinError','Cannot initialize server'
self.servThread = _ServerThread(xmlServ,verbose=self.verbose)
self.servThread.setName('XML-RPC Server')
self.servThread.start()
def OnClose(self,event):
""" be sure to shutdown the server and the server thread before
leaving
def RPCOnClose(self,event):
""" callback for when the application is closed
be sure to shutdown the server and the server thread before
leaving
"""
self._xmlServ = None
self.servThread = None
# by setting the global stopEvent we inform the server thread
# that it's time to shut down.
stopEvent.set()
if event is not None:
# if we came in here from a user event (as opposed to an RPC event),
# then we'll need to kick the server one last time in order
# to get that thread to terminate. do so now
s1 = xmlrpclib.Server('http://localhost:%d'%(self.rpcPort))
try:
s1._rpcPing()
except:
pass
if self._origOnClose is not None:
self._origOnClose(event)
def RPCQuit(self):
""" shuts down everything, including the rpc server
"""
self.RPCOnClose(None)
def OnExternal(self,event):
""" this is the callback used to handle RPCs
**Arguments**
- event: an _ExternalEvent_ sent by the rpc server
Exceptions are caught and returned in the global _rpcStatus
structure. This allows the xmlrpc server to report the
exception to the client without mucking up any of the delicate
thread stuff.
"""
event.rpcStatusLock.acquire()
doQuit = 0
try:
res = eval('apply(self.%s,event.args)'%event.method)
except:
import sys,traceback
traceback.print_exc()
event.rpcStatus.result = sys.exc_info()[:2]
methsplit = string.split(event.method,'.')
meth = self
for piece in methsplit:
meth = getattr(meth,piece)
except AttributeError,msg:
event.rpcStatus.result = 'No Such Method',msg
event.rpcStatus.status = rpcEXCEPT
else:
if res is None:
event.rpcStatus.result = []
try:
res = apply(meth,event.args)
except:
import traceback
if self.verbose: traceback.print_exc()
event.rpcStatus.result = sys.exc_info()[:2]
event.rpcStatus.status = rpcEXCEPT
else:
event.rpcStatus.result = res
event.rpcStatus.status = rpcDONE
if res is None:
# returning None across the xmlrpc interface is problematic
event.rpcStatus.result = []
else:
event.rpcStatus.result = res
event.rpcStatus.status = rpcDONE
event.rpcStatusLock.release()
# broadcast (using the condition var) that we're done with the event
event.rpcCondVar.acquire()
event.rpcCondVar.notify()
event.rpcCondVar.release()
if __name__ == '__main__':
import sys
port = 800
if len(sys.argv)>1:
port = int(sys.argv[1])
import time
if sys.platform == 'win32':
port = 800
else:
port = 8023
class rpcFrame(wxFrame,rpcMixin):
"""A simple wxFrame with the rpcMixin functionality added
@@ -199,6 +357,9 @@ if __name__ == '__main__':
if kwargs.has_key('rpcPort'):
mixinArgs['port'] = kwargs['rpcPort']
del kwargs['rpcPort']
if kwargs.has_key('rpcPortScan'):
mixinArgs['portScan'] = kwargs['rpcPortScan']
del kwargs['rpcPortScan']
apply(wxFrame.__init__,(self,)+args,kwargs)
apply(rpcMixin.__init__,(self,),mixinArgs)
@@ -220,17 +381,38 @@ if __name__ == '__main__':
def OnClose(self,event):
self.Destroy()
class MyApp(wxApp):
def OnInit(self):
frame = rpcFrame(NULL, -1, "wxPython RPCDemo", wxDefaultPosition, wxSize(300,300),rpcHost='localhost',rpcPort=port)
frame.Show(TRUE)
import time
#self.SetTopWindow(frame)
frame2 = rpcFrame(NULL, -1, "wxPython RPCDemo2", wxDefaultPosition, wxSize(300,300),rpcHost='localhost',rpcPort=port+1)
frame2.Show(TRUE)
self.frame = rpcFrame(NULL, -1, "wxPython RPCDemo", wxDefaultPosition,
wxSize(300,300),
rpcHost='localhost',rpcPort=port)
self.frame.Show(TRUE)
return TRUE
app = MyApp(0)
app.MainLoop()
def testcon(port):
s1 = xmlrpclib.Server('http://localhost:%d'%(port))
s1.SetTitle('Munged')
s1._rpcPing()
if doQuit:
s1.RPCQuit()
doQuit = 1
if len(sys.argv)>1 and sys.argv[1] == '-q':
doQuit = 0
nT = threading.activeCount()
app = MyApp(0)
activePort = app.frame.rpcPort
t = threading.Thread(target=lambda x=activePort:testcon(x),verbose=0)
t.start()
app.MainLoop()
# give the threads time to shut down
if threading.activeCount() > nT:
print 'waiting for all threads to terminate'
while threading.activeCount() > nT:
time.sleep(0.5)