|
|
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN
|
|
|
|
"http://www.w3.org/TR/html4/loose.dtd"">
|
|
|
|
<html>p
|
|
|
|
<head>
|
|
|
|
<title>Examples</title>
|
|
|
|
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
|
|
|
<meta name="GENERATOR" content="Quanta Plus">
|
|
|
|
</head>
|
|
|
|
<p>
|
|
|
|
<DIV
|
|
|
|
CLASS="NAVHEADER"
|
|
|
|
><TABLE SUMMARY="Header navigation table" WIDTH="100%" BORDER="0" CELLPADDING="0" CELLSPACING="0">
|
|
|
|
<TR><TH COLSPAN="3" ALIGN="center">Python Bindings for KDE (PyKDE-3.16.0)</TH></TR>
|
|
|
|
<TR><TD WIDTH="10%" ALIGN="left" VALIGN="bottom"><A HREF="examples.html" ACCESSKEY="P">Prev</A></TD>
|
|
|
|
<TD WIDTH="80%" ALIGN="center" VALIGN="bottom"></TD>
|
|
|
|
<TD WIDTH="10%" ALIGN="right" VALIGN="bottom"><A HREF="limits.html" ACCESSKEY="N">Next</A></TD>
|
|
|
|
</TR>
|
|
|
|
</TABLE><HR ALIGN="LEFT" WIDTH="100%"></DIV>
|
|
|
|
|
|
|
|
<h1>DCOP and Extensions</h1>
|
|
|
|
<p>
|
|
|
|
DCOP is KDE's acronym for it's "Desktop Communications-Oriented Protocol" - basically a
|
|
|
|
lightweight and simple mechanism for inter-process communications (IPC). DCOP allows two
|
|
|
|
running applications to exchange messages or other information or exercise control over
|
|
|
|
each other.
|
|
|
|
</p>
|
|
|
|
<p>
|
|
|
|
While the DCOP implementation is convenient for C++ programmers, it presents some difficulties
|
|
|
|
for Python programmers. The DCOP extensions that have been added to PyKDE should make most
|
|
|
|
DCOP applications (either DCOP-client or DCOP-enabled applications) simple to write and
|
|
|
|
reliable to run
|
|
|
|
</p>
|
|
|
|
<h2>What Extensions?</h2>
|
|
|
|
There are three basic extensions added to PyKDE that are not part of KDE itself:
|
|
|
|
<dl>
|
|
|
|
<dt>Packing/Unpacking TQByteArrays</dt>
|
|
|
|
<dd>
|
|
|
|
DCOP passes data between applications using TQByteArrays. TQByteArrays can be difficult to
|
|
|
|
pack or unpack using PyTQt or PyKDE, so PyKDE has additional methods (dcop_add and dcop_next)
|
|
|
|
to make these operations simpler in Python
|
|
|
|
</dd>
|
|
|
|
<dt>Client Extensions</dt>
|
|
|
|
<dd>
|
|
|
|
PyKDE's DCOP client extensions make it easy and natural to call DCOP methods in other
|
|
|
|
DCOP-enabled applications - the application or DCOP object being referenced look like
|
|
|
|
Python classes, and the method being called looks to the programmer like a Python method.
|
|
|
|
</dd>
|
|
|
|
<dt>DCOP Enabling (Export) Extensions</dt>
|
|
|
|
<dd>
|
|
|
|
Another set of extensions makes it trivial to expose an application's methods via DCOP to
|
|
|
|
other applications. All that is required is to subclass a pre-written Python class and
|
|
|
|
provide a list of the methods to expose, along with a method signature listing the name
|
|
|
|
of the method, it's return type, and the the types of its arguments.
|
|
|
|
</dd>
|
|
|
|
</dl>
|
|
|
|
<p>
|
|
|
|
The methods for packing/unpacking TQByteArrays are available to the programmer, but are
|
|
|
|
primarily used transparently by the other PyKDE DCOP extensions. The client and export extensions
|
|
|
|
are two Python modules that are included and installed as part of PyKDE.
|
|
|
|
</p>
|
|
|
|
|
|
|
|
<h2>Calling DCOP Methods</h2>
|
|
|
|
<p>
|
|
|
|
Accessing a DCOP method in another application requires 3 pieces of information: the name of
|
|
|
|
the application to be accessed, the name of the DCOP object which holds the method to be
|
|
|
|
called, and the name of the method itself.
|
|
|
|
</p>
|
|
|
|
|
|
|
|
<h3>Collection the Information</h3>
|
|
|
|
<p>
|
|
|
|
The easiest way to collect the required information is to use the kdcop application that
|
|
|
|
comes with PyKDE. kdcop is graphical application that looks like the image shown.
|
|
|
|
</p>
|
|
|
|
<IMG src="images/kdcop1.png" align="middle" border="0">
|
|
|
|
<h3>Application/Object/Method Information</h3>
|
|
|
|
<p>
|
|
|
|
Look at the entry for kicker, which has been expanded in the image. Underneath kicker (the
|
|
|
|
application name - kicker is the panel on the standard KDE screen) is a list of DCOP objects,
|
|
|
|
for example, Panel. Under each object is a list of methods the application/object exposes, for
|
|
|
|
example, "int panelPosition ()". This indicates the method panelPosition takes no arguments
|
|
|
|
and returns an integer.
|
|
|
|
</p>
|
|
|
|
<h3>Writing the Code</h3>
|
|
|
|
<p>
|
|
|
|
There are two ways to use the DCOP extensions to call the panelPosition method. The first is
|
|
|
|
from the application level, the second is from the object level. We can use the "application
|
|
|
|
level" in this case, because the object name "Panel" can be valid Python identifier (not all
|
|
|
|
object names have this property).
|
|
|
|
</p>
|
|
|
|
<TABLE BORDER="0" BGCOLOR="#E0E0E0" WIDTH="100%">
|
|
|
|
<TR><TD><PRE CLASS="PROGRAMLISTING">
|
|
|
|
import dcopext
|
|
|
|
# ! other imports not shown !
|
|
|
|
|
|
|
|
app = KApplication ()
|
|
|
|
dcop = app.dcopClient ()
|
|
|
|
|
|
|
|
d = dcopext.DCOPApp ("kicker", dcop)
|
|
|
|
ok, panelPos = d.Panel.panelPosition ()
|
|
|
|
|
|
|
|
</PRE></TD></TR></TABLE>
|
|
|
|
<p>
|
|
|
|
That's all there's to it in this case. We import dcopext, which contains the client extension
|
|
|
|
classes; from the KApplication instance, we "borrow" the DCOPClient instance (dcop); we create a
|
|
|
|
DCOPApp instance, passing it the name of the app ("kicker") and the DCOPClient instance; we
|
|
|
|
call kicker's Panel object's panelPosition method (d.Panel.panelPosition); lastly, the integer
|
|
|
|
value is returned to our application (panelPos) as the second item in a tuple - the first element
|
|
|
|
of the tuple (ok) is a boolean value indicating whether the call succeeded (True) or failed (False).
|
|
|
|
</p>
|
|
|
|
<p>
|
|
|
|
Many of the DCOP object names can't be used as Python identifiers (for example,"0x8239ae0" or
|
|
|
|
KIO::Scheduler in kicker, or EditInterface#1, which kwrite exports). In that case, it's
|
|
|
|
necessary to write the code at the object level, constructing a DCOPObj instead of a
|
|
|
|
DCOPApp (DCOPApp actually constructs a DCOPObj behind the scenese in the example above).
|
|
|
|
</p>
|
|
|
|
<TABLE BORDER="0" BGCOLOR="#E0E0E0" WIDTH="100%">
|
|
|
|
<TR><TD><PRE CLASS="PROGRAMLISTING">
|
|
|
|
import dcopext
|
|
|
|
# ! other imports not shown !
|
|
|
|
|
|
|
|
o = dcopext.DCOPObj ("kicker", dcop, "Panel")
|
|
|
|
ok, panelPos = o.panelPosition ()
|
|
|
|
|
|
|
|
</PRE></TD></TR></TABLE>
|
|
|
|
<p>
|
|
|
|
In this example, 'o' is a DCOPObj. In constructing 'o', we add a string representation of
|
|
|
|
the name of the object ("Panel") to the application name and DCOPClient object. We then
|
|
|
|
use the DCOPObj 'o' to call the the method (panelPosition) that the object supports.
|
|
|
|
</p>
|
|
|
|
<h3>More on Application Names</h3>
|
|
|
|
<p>
|
|
|
|
In the example above, kicker was the name of the application and the id we used to reference
|
|
|
|
the application as well. kicker is an example of a unique application - only one instance of
|
|
|
|
kicker can be running at any time.
|
|
|
|
</p>
|
|
|
|
<p>
|
|
|
|
Many applications (konqueror, for example) can have several instances running at the same
|
|
|
|
time. kdcop would display multiple instances like this:
|
|
|
|
</p>
|
|
|
|
<IMG src="images/kdcop2.png" border="0">
|
|
|
|
<p>
|
|
|
|
kdcop shows 3 instances of konqueror running in the example above. To perform a DCOP call in
|
|
|
|
this case, we'd need to know which instance of konqueror we want to send the call to. The
|
|
|
|
suffix on each instance of konqueror is the PID of the instance running. We simply pass the
|
|
|
|
full id (app name + pid - eg konqueror-14409) when constructing DCOPApp or DCOPObj.
|
|
|
|
</p>
|
|
|
|
<p>
|
|
|
|
If you instantiate the application you want to communicate with from your own application (that
|
|
|
|
will be making the DCOP calls), methods like KApplication.startServiceByDesktopName will
|
|
|
|
let you start the app and also return both the PID of the started app and the complete
|
|
|
|
identifier string needed to initiate DCOP communications. The identifier's name portion may or
|
|
|
|
may not be the same as the name of the application (see the example_dcopexport.py example program,
|
|
|
|
whose ID is "petshop-####" (#### is the PID of the application instance).
|
|
|
|
</p>
|
|
|
|
<h3>Data Types</h3>
|
|
|
|
The DCOP extensions will support any of the following C++ data types:
|
|
|
|
<table><TR><TD>char</TD><TD>short</TD><TD>int</TD></TR>
|
|
|
|
<TR><TD>long</TD><TD>unsigned char</TD><TD>unsigned short</TD></TR>
|
|
|
|
<TR><TD>unsigned int</TD><TD>unsigned long</TD><TD>uchar</TD></TR>
|
|
|
|
<TR><TD>ushsort</TD><TD>uint</TD><TD>ulong</TD></TR>
|
|
|
|
<TR><TD>Q_INT32</TD><TD>pid_t</TD><TD>float</TD></TR>
|
|
|
|
<TR><TD>double</TD><TD>TQString</TD><TD>TQStringList</TD></TR>
|
|
|
|
<TR><TD>TQCString</TD><TD>KURL</TD><TD>KURL::List</TD></TR>
|
|
|
|
<TR><TD>TQSize</TD><TD>TQRect</TD><TD>TQRegion</TD></TR>
|
|
|
|
<TR><TD>TQFont</TD><TD>TQCursor</TD><TD>TQPixmap</TD></TR>
|
|
|
|
<TR><TD>TQColor</TD><TD>TQColorGroup</TD><TD>TQPalette</TD></TR>
|
|
|
|
<TR><TD>TQBrush</TD><TD>TQWidget::FocusPolicy</TD><TD>DCOPRef</TD></TR>
|
|
|
|
<TR><TD>TQVariant</TD><TD>TQDate</TD><TD>TQTime</TD></TR>
|
|
|
|
<TR><TD>TQDateTime</TD><TD>TQImage</TD><TD>TQKeySequence</TD></TR>
|
|
|
|
<TR><TD>TQPen</TD><TD>TQPicture</TD><TD>TQPointArray</TD></TR>
|
|
|
|
<TR><TD>TQValueList<DCOPRef></TD><TD>TQValueList<TQCString></TD><TD>TQMap<TQCString,DCOPRef></TD></TR>
|
|
|
|
<TR><TD>TQMap<TQCString,DCOPRef></TD><TD></TD><TD></TD></TR>
|
|
|
|
</table>
|
|
|
|
<p>
|
|
|
|
Data conversion between C++ and Python types is done transparently. The integer types
|
|
|
|
map to Python int or Python long, the decimal types to Python double. A Python string
|
|
|
|
can be used for any argument that requires a TQString or TQCString (return types will
|
|
|
|
always be the TQt object type). The TQValueList types take or return a Python list of the
|
|
|
|
indicated object. The TQMap types take or return a Python dict with the first type as
|
|
|
|
the key and the second type as data. All other types use the same object type in
|
|
|
|
Python and TQt (for instance, TQPoint or TQStringList).
|
|
|
|
</p>
|
|
|
|
<p>
|
|
|
|
It's possible to add support for more types in the future. To be added, a type requires
|
|
|
|
a pair of overloaded TQDataStream operators ("<<" and ">>"). Types must also
|
|
|
|
exist in the libs that PyTQt and PyKDE support - types specific to applications (like
|
|
|
|
konqueror) cannot be supported at this time.
|
|
|
|
</p>
|
|
|
|
<h3>Other Extension Features</h3>
|
|
|
|
<p>
|
|
|
|
The dcopext module consists of 3 classes (DCOPApp, DCOPObj and DCOPMeth) corresponding to
|
|
|
|
applications, objects and methods respectively. These classes have additional variables and methods:
|
|
|
|
<ul>
|
|
|
|
<li> DCOPApp.objects - returns a list of the applications DCOP objects. example: d.objects</li>
|
|
|
|
<li> DCOPApp.object(objname) - returns a DCOPObj for the DCOPObject. example: d.object ("Panel")</li>
|
|
|
|
<li> DCOPObj.methods - returns a list of the methods and object has. example: o.methods</li>
|
|
|
|
<li> DCOPObj.method (methname) - returns an DCOPMeth instance corresponding to the method, which
|
|
|
|
can be called. example: o.method("panelPosition")</li>
|
|
|
|
<li> DCOPMeth.valid - returns whether the method is valid or not (True/False). example:
|
|
|
|
d.Panel.panelPosition.valid</li>
|
|
|
|
<li>DCOPMeth.rtype - a method's return type. example d.Panel.panelPosition.rtype</li>
|
|
|
|
<li>DCOPMeth.argtypes - a list of the method's argument types. example d.Panel.panelPosition.argtypes</li>
|
|
|
|
<li>DCOPMeth.argnames - a list of the method's argument names. example d.Panel.panelPosition.argnames</li>
|
|
|
|
</ul>
|
|
|
|
<p>
|
|
|
|
If a method isn't valid, it's rtype, argtypes and argnames values will all be None.
|
|
|
|
</p>
|
|
|
|
</p>
|
|
|
|
<h2>DCOP Enabling a Python Application</h2>
|
|
|
|
<p>
|
|
|
|
Enabling a Python application to handle DCOP calls is even simpler than making calls as a
|
|
|
|
DCOP client. Suppose a Python application has two methods we want to appear as int getValue()
|
|
|
|
and void setValue(int). The corresponding Python methods are get_value() set_value(i).
|
|
|
|
We want to export these methods under the object "Value". Here's the code:
|
|
|
|
</p>
|
|
|
|
<TABLE BORDER="0" BGCOLOR="#E0E0E0" WIDTH="100%">
|
|
|
|
<TR><TD><PRE CLASS="PROGRAMLISTING">
|
|
|
|
from dcopexport import DCOPExObj
|
|
|
|
# ! other imports not shown !
|
|
|
|
|
|
|
|
class ValueObject (DCOPExObj):
|
|
|
|
def __init__ (self, id="Value"):
|
|
|
|
DCOPExObj.__init__ (self, id)
|
|
|
|
self.value = 0
|
|
|
|
|
|
|
|
self.addMethod ("int getValue()", self.get_value)
|
|
|
|
self.addMethod ("void setValue(int)", self.set_value)
|
|
|
|
|
|
|
|
def get_value(self):
|
|
|
|
return self.value
|
|
|
|
|
|
|
|
def set_value (self, i):
|
|
|
|
self.value = i
|
|
|
|
|
|
|
|
</PRE></TD></TR></TABLE>
|
|
|
|
<p>
|
|
|
|
Note that the module for the DCOPExObj class is "dcopexport". The Python methods may be
|
|
|
|
part of the DCOPExObj subclass, part of another class, or global Python functions. They
|
|
|
|
must be callable from the DCOPExObj subclass being created. The dcopexport extension takes
|
|
|
|
care of everything else, including the "functions()" method which applications (yours or
|
|
|
|
kdcop, for example) can call to find out which methods are available and their return
|
|
|
|
and argument types. You can have multiple instances of DCOPExObj in a program. All of
|
|
|
|
the data types listed above are supported transparently - you don't have to pack or
|
|
|
|
unpack TQByteArrays.
|
|
|
|
</p>
|
|
|
|
<h2>Packing and Unpacking TQByteArrays</h2>
|
|
|
|
<p>
|
|
|
|
NOTE: It isn't necessary to use the dcop_add and dcop_next functions or worry about
|
|
|
|
TQByteArrays at all when using dcopext or dcopexport as shown above. Those modules
|
|
|
|
handle the packing and unpacking details automatically behind the scenes.
|
|
|
|
</p>
|
|
|
|
<p>
|
|
|
|
The dcop_add and dcop_next functions are available in the PyKDE tdecore module (they
|
|
|
|
may be relocated to a different module in the future). They use a TQDataStream to operate
|
|
|
|
on a TQByteArray. The TQByteArray can be thought of as a stack (a FIFO stack though) -
|
|
|
|
dcop_add pushes objects onto the stack, dcop_next pops objects off the stack. The first
|
|
|
|
object popped off will be the first object pushed on, etc.
|
|
|
|
</p>
|
|
|
|
<p>
|
|
|
|
The dcop_add function is actually a group of overloaded functions, some of which take
|
|
|
|
different argument counts. Here are some examples:
|
|
|
|
</p>
|
|
|
|
<TABLE BORDER="0" BGCOLOR="#E0E0E0" WIDTH="100%">
|
|
|
|
<TR><TD><PRE CLASS="PROGRAMLISTING">
|
|
|
|
from tdecore import dcop_add, dcop_next
|
|
|
|
from qt import TQByteArray, TQDataStream, IO_ReadOnly, IO_WriteOnly, TQString,\
|
|
|
|
TQCString, TQValueList<TQCString>
|
|
|
|
from dcopext import numericTypes, stringTypes
|
|
|
|
|
|
|
|
b = TQByteArray ()
|
|
|
|
s = TQDataStream (b, IO_WriteOnly)
|
|
|
|
|
|
|
|
i = 6
|
|
|
|
d = 3.14
|
|
|
|
t = TQString ("Hello, World")
|
|
|
|
x = TQCString ("One")
|
|
|
|
y = TQCString ("Two")
|
|
|
|
z = TQCString ("Three")
|
|
|
|
l = [x, y, z]
|
|
|
|
|
|
|
|
dcop_add (s, i, "long")
|
|
|
|
dcop_add (s, d, "double")
|
|
|
|
dcop_add (s, t)
|
|
|
|
dcop_add (s, x)
|
|
|
|
dcop_add (s, l, "TQValueList<TQCString>")
|
|
|
|
|
|
|
|
</PRE></TD></TR></TABLE>
|
|
|
|
<p>
|
|
|
|
Notice that for numeric types (integer or decimal) an additional string is needed to
|
|
|
|
specify the C++ type of the object - that's because Python has only 3 basic numeric
|
|
|
|
types, while C++ has at least 10 basic numeric types plus variations via typedefs.
|
|
|
|
</p>
|
|
|
|
<p>
|
|
|
|
Also, the TQValueList (and TQMap - not shown) type needs a qualifier - a Python list
|
|
|
|
type doesn't know (or care) what the type of its elements is.
|
|
|
|
</p>
|
|
|
|
<p>
|
|
|
|
Other types (TQString, TQCString) are uniquely typed, so no modifier is needed.
|
|
|
|
</p>
|
|
|
|
<p>
|
|
|
|
While it may change in the future, dcop_add right now retains the variable argument lists.
|
|
|
|
You can handle this in your own code easily if you import "numericTypes" and
|
|
|
|
"stringTypes" from dcopext as shown above. The following code will sort things out:
|
|
|
|
</p>
|
|
|
|
|
|
|
|
<TABLE BORDER="0" BGCOLOR="#E0E0E0" WIDTH="100%">
|
|
|
|
<TR><TD><PRE CLASS="PROGRAMLISTING">
|
|
|
|
# atype is the type of the argument being processed (as a string)
|
|
|
|
# value is the object being packed into the TQByteArray
|
|
|
|
|
|
|
|
if atype in numericTypes:
|
|
|
|
dcop_add (s, value, atype)
|
|
|
|
elif atype in stringTypes and isinstance (value, str):
|
|
|
|
dcop_add (s, eval ("%s('%s')" % (atype, value)))
|
|
|
|
elif atype.startswith ("TQMap") or atype.startswith ("TQValueList"):
|
|
|
|
dcop_add (params, value, atype)
|
|
|
|
else:
|
|
|
|
dcop_add (s, value)
|
|
|
|
|
|
|
|
</PRE></TD></TR></TABLE>
|
|
|
|
<p>
|
|
|
|
At least in DCOP related applications, all of the necessary type information is always
|
|
|
|
easily available. The first if clause above processes numeric types; the second if
|
|
|
|
clause allows you to use Python strings in place of TQt's TQString or TQCString types; the
|
|
|
|
third if clause handles TQValueList and TQMap based types; the else clause handles
|
|
|
|
everything else.
|
|
|
|
</p>
|
|
|
|
<p>
|
|
|
|
Unpacking a TQByteArray is simpler - dcop_next always takes a TQDataStream instance and
|
|
|
|
a type name string. The code below assumes the same set of imports as above:
|
|
|
|
</p>
|
|
|
|
<TABLE BORDER="0" BGCOLOR="#E0E0E0" WIDTH="100%">
|
|
|
|
<TR><TD><PRE CLASS="PROGRAMLISTING">
|
|
|
|
|
|
|
|
# b is a TQByteArray to be unpacked
|
|
|
|
s = TQDataStream (b, IO_ReadOnly)
|
|
|
|
|
|
|
|
i1 = dcop_next (s, "long")
|
|
|
|
d1 = dcop_next (s, "double")
|
|
|
|
t1 = dcop_next (s, "TQString")
|
|
|
|
x1 = dcop_next (s, "TQCString")
|
|
|
|
l1 = dcop_next (s, "TQValueList<TQCString>")
|
|
|
|
|
|
|
|
</PRE></TD></TR></TABLE>
|
|
|
|
<p>
|
|
|
|
Of course the type specified in dcop_next to unpack the object must match the type of
|
|
|
|
the object originally packed, and must happen in the same order (you can't use this to cast or convert types). i1, d1, etc
|
|
|
|
should contain the same values as i, d, etc above.
|
|
|
|
</p>
|
|
|
|
<p>
|
|
|
|
The types that dcop_add/dcop_next can handle are the same types listed in the dcopext
|
|
|
|
section above.
|
|
|
|
</p>
|
|
|
|
<h2>Thanks</h2>
|
|
|
|
<p>
|
|
|
|
The code for dcopext and dcopexport is based on pydcop.py and pcop.cpp written by Torben Weis
|
|
|
|
and Julian Rockey. It's available in the dcoppython/ section of the kde-bindings source code,
|
|
|
|
and can be used to implement DCOP communication without using PyTQt or PyKDE.
|
|
|
|
</p>
|
|
|
|
|
|
|
|
<DIV CLASS="NAVFOOTER">
|
|
|
|
<HR ALIGN="LEFT" WIDTH="100%">
|
|
|
|
<TABLE SUMMARY="Footer navigation table" WIDTH="100%" BORDER="0" CELLPADDING="0" CELLSPACING="0" >
|
|
|
|
<TR>
|
|
|
|
<TD WIDTH="33%" ALIGN="left" VALIGN="top"><A HREF="examples.html" ACCESSKEY="P">Prev</A></TD>
|
|
|
|
<TD WIDTH="34%" ALIGN="center" VALIGN="top"><A HREF="index.html" ACCESSKEY="H">Home</A></TD>
|
|
|
|
<TD WIDTH="33%" ALIGN="right" VALIGN="top"><A HREF="limits.html" ACCESSKEY="N">Next</A></TD>
|
|
|
|
</TR>
|
|
|
|
<TR>
|
|
|
|
<TD WIDTH="33%" ALIGN="left" VALIGN="top">Templates and Example Programs</TD>
|
|
|
|
<TD WIDTH="34%" ALIGN="center" VALIGN="top"> </TD>
|
|
|
|
<TD WIDTH="33%" ALIGN="right" VALIGN="top">General Limitations</TD>
|
|
|
|
</TR>
|
|
|
|
</TABLE>
|
|
|
|
</DIV>
|
|
|
|
|
|
|
|
</body>
|
|
|
|
</html>
|