Explanations of the examples here

This commit is contained in:
Jack Jansen 1996-03-18 13:38:52 +00:00
parent f4875af09c
commit a6308130bb
5 changed files with 1019 additions and 0 deletions

171
Mac/Demo/applescript.html Normal file
View File

@ -0,0 +1,171 @@
<HTML><HEAD><TITLE>Using Open Scripting Extension from Python</TITLE></HEAD>
<BODY>
<H1>Using Open Scripting Extension from Python</H1>
<HR>
OSA support in Python is still far from complete, and what
support there is is likely to change in the forseeable future. Still,
there is already enough in place to allow you to do some nifty things
to other programs from your python program. <P>
<CITE>
Actually, when we say "AppleScript" in this document we actually mean
"the Open Scripting Architecture", there is nothing
AppleScript-specific in the Python implementation. <p>
</CITE>
In this example, we will look at a scriptable application, extract its
"AppleScript Dictionary" and generate a Python interface module from
that and use that module to control the application. Because we want
to concentrate on the OSA details we don't bother with a real
user-interface for our application. <p>
The application we are going to script is Eudora Light, a free mail
program from <A HREF="http://www.qualcomm.com">QualComm</A>. This is a
very versatile mail-reader, and QualComm has an accompanying
commercial version once your needs outgrow Eudora Light. Our program
will tell Eudora to send queued mail, retrieve mail or quit. <p>
<H2>Creating the Python interface module</H2>
There is a tool in the standard distribution that looks through a file
for an 'AETE' or 'AEUT' resource, the internal representation of the
AppleScript dictionary. This tool is called
<CODE>gensuitemodule.py</CODE>, and lives in
<CODE>Tools:bgen:ae</CODE>. When we start it, it asks us for an input
file and we point it to the Eudora Light executable. It starts parsing
the AETE resource, and for each AppleEvent suite it finds it prompts
us for the filename of the resulting python module. Remember to change
folders for the first module, you don't want to clutter up the Eudora
folder with your python interfaces. If you want to skip a suite you
press cancel and the process continues with the next suite. In the
case of Eudora, you do <EM>not</EM> want to generate the Required
suite, because it will be empty. AppleScript understands that an empty
suite means "incorporate the whole standard suite by this name",
gensuitemodule does not currently understand this. Creating the empty
<CODE>Required_Suite.py</CODE> would hide the correct module of that
name from our application. <p>
<CITE>
Time for a sidebar. If you want to re-create
<CODE>Required_Suite.py</CODE> or one of the other standard modules
you should look in <CODE>System Folder:Extensions:Scripting
Additions:Dialects:English Dialect</CODE>, that is where the core
AppleEvent dictionaries live. Also, if you are looking for the
<CODE>Finder_Suite</CODE> interface: don't look in the finder (it has
an old System 7.0 scripting suite), look at the extension <CODE>Finder
Scripting Extension</CODE>. <p>
</CITE>
Let's glance at the <A
HREF="scripting/Eudora_Suite.py">Eudora_Suite.py</A> just created. You
may want to open Script Editor alongside, and have a look at how it
interprets the dictionary. EudoraSuite.py starts with some
boilerplate, then come some dictionaries implementing the OSA
Enumerations, then a big class definition with methods for each
AppleScript Verb and finally some comments. The Enumerations we will
skip, it suffices to know that whenever you have to pass an enumerator
to a method you can pass the english name and don't have to bother
with the 4-letter type code. So, you can say
<CODE><PRE>
eudora.notice(occurrence="mail_arrives")
</PRE></CODE>
instead of the rather more cryptic
<CODE><PRE>
eudora.notice(occurrence="wArv")
</PRE></CODE>
The <CODE>Eudora_Suite</CODE> class is the bulk of the code
generated. For each verb it contains a method. Each method knows what
arguments the verb expects, and it makes handy use of the keyword
argument scheme introduced in Python 1.3 to present a palatable
interface to the python programmer. You will see that each method
calls some routines from <CODE>aetools</CODE>, an auxiliary module
living in <CODE>Tools:bgen:ae</CODE> which contains some other nifty
AppleEvent tools as well. Have a look at it sometime, there is (of
course) no documentation yet. <p>
The other thing you notice is that each method calls
<CODE>self.send</CODE>, but no such method is defined. You will have
to provide it by subclassing or multiple inheritance, as we shall see
later. <p>
The module ends with some comments. Sadly, gensuitemodule is not yet
able to turn the Object Specifiers into reasonable Python code. For
now, if you need object specifiers, you will have to use the routines
defined in <CODE>aetools.py</CODE> (and <CODE>aetypes.py</CODE>, which
it incorporates). You use these in the form <CODE>aetools.Word(10,
aetools.Document(1))</CODE> where the corresponding AppleScript
terminology would be <CODE>word 10 of the first
document</CODE>. Examine the two modules mentioned above along with
the comments at the end of your suite module if you need to create
more than the standard object specifiers. <p>
<H2>Using a Python suite module</H2>
Now that we have created the suite module we can use it in an
application. We do this by creating a class that inherits
<CODE>Eudora_Suite</CODE> and the <CODE>TalkTo</CODE> class from
<CODE>aetools</CODE>. The <CODE>TalkTo</CODE> class is basically a
container for the <CODE>send</CODE> method used by the methods from
the suite classes. <p>
Actually, our class will also inherit <CODE>Required_Suite</CODE>,
because we also need functionality from that suite: the quit
command. Gensuitemodule could have created this completely derived
class for us, since it has access to all information needed to build
the class but unfortunately it does not do so at the moment. All in
all, the heart of our program looks like this:
<CODE><PRE>
import Eudora_Suite, Required_Suite, aetools
class Eudora(aetools.TalkTo, Required_Suite.Required_Suite, \
Eudora_Suite.Eudora_Suite):
pass
</PRE></CODE>
Yes, our class body is <CODE>pass</CODE>, all functionality is already
provided by the base classes, the only thing we have to do is glue it
together in the right way. <p>
Looking at the sourcefile <A
HREF="scripting/testeudora.py">testeudora.py</A> we see that it starts
with some imports (and some <CODE>addpack</CODE> calls to extend
<CODE>sys.path</CODE> to include <CODE>Tools:bgen:ae</CODE>, use of
<CODE>ni</CODE> should be preferred over <CODE>addpack</CODE> but I
have not managed to master it yet). Then we get the class definition
for our main object and a constant giving the signature of Eudora. <p>
This, again, needs a little explanation. There are various ways to
describe to AppleScript which program we want to talk to, but the
easiest one to use (from Python, at least) is creator
signature. Application name would be much nicer, but Python currently
does not have a module that interfaces to the Finder database (which
would allow us to map names to signatures). The other alternative,
<CODE>ChooseApplication</CODE> from the program-to-program toolbox, is
also not available from Python at the moment. <p>
The main program itself is a wonder of simplicity. We create the
object that talks to Eudora (passing the signature as argument), ask
the user what she wants and call the appropriate method of the talker
object. The use of keyword arguments with the same names as used by
AppleScript make passing the parameters a breeze. <p>
The exception handling does need a few comments, though. Since
AppleScript is basically a connectionless RPC protocol nothing happens
when we create to talker object. Hence, if the destination application
is not running we will not notice until we send our first
command. There is another thing to note about errors returned by
AppleScript calls: even though <CODE>MacOS.Error</CODE> is raised not
all of the errors are actually <CODE>OSErr</CODE>-type errors, some
are error codes returned by the server application. In that case, the
error message will be incorrect. <p>
That concludes our simple example. Again, let me emphasize that
scripting support in Python is not very complete at the moment, and
the details of how to use AppleEvents will definitely change in the
near future. This will not only fix all the ideosyncracies noted in
this document but also break existing programs, since the current
suite organization will have to change to fix some of the problems.
Still, if you want to experiment with AppleEvents right now: go ahead!
<p>

211
Mac/Demo/example1.html Normal file
View File

@ -0,0 +1,211 @@
<HTML><HEAD><TITLE>Using python to create Macintosh applications, part one</TITLE></HEAD>
<BODY>
<H1>Using python to create Macintosh applications, part one</H1>
<HR>
This document will show you how to create a simple mac-style
application using Python. We will glance at how to use dialogs and
resources. <p>
The example application we look at will be a simple program with a
dialog that allows you to control and monitor InterSLIP, a device
driver that connects your mac to the Internet via a modem connection.
<A HREF="example1/InterslipControl-1.py">Source</A> and resource file
(in binary and <A
HREF="example1/InterslipControl-1.rsrc.hqx">BinHex</A> form for
downloading) for this application are available in the <A
HREF="example1">example1</A> folder (which you will have to download
if you are reading this document over the net and if you want to look
at the resources). <p>
We will use a C extension module module "interslip" that allows a
Python program to control and monitor the behaviour of the low-level
driver, and we will create the user interface around that. If you want
to actually run the code, you will obvously need InterSLIP and the
interslip module. The latter is available as a dynamically loadable
extension for PowerPC macs, and may be compiled in your Python
interpreter for 68K macs. As of this writing there is still a slight
problem with the Python interslip module causing it to say "file not
found" if the driver is not loaded yet. The workaround is to load the
driver by starting InterSLIP Control and quitting it. <p>
<CITE>
If you are interested in building your own extensions to python you
should check out the companion document <A
HREF="plugins.html">Creating Macintosh Python C extensions</A>,
which tells you how to build your own C extension. Not completely
coincidental this document uses the interslip module that we will use
here as an example. <p>
</CITE>
<H2><A NAME="dialog-resources">Creating dialog resources</A></H2>
Let us start with the creative bit: building the dialogs and creating
an icon for our program. For this you need ResEdit, and a reasonable
working knowledge of how to use it. "Inside Mac" or various books on
macintosh programming will help here. <p>
There is one fine point that deserves to be mentioned here: <A
NAME="resource-numbering">resource numbering</A>. Because often your
resources will be combined with those that the Python interpreter and
various standard modules need you should give your DLOG and DITL
resources numbers above 512. 128 and below are reserved for Apple,
128-255 for the Python interpreter and 256-511 for standard
modules. If you are writing a module that you will be distributing for
inclusion in other people's programs you may want to register a number
in the 256-511 range, contact Guido or myself or whoever you think is
"in charge" of Python for the Macintosh at the moment. Even though the
application we are writing at the moment will keep its resources in a
separate resource file it is still a good idea to make sure that no
conflicts arise: once you have opened your resource file any attempt
by the interpreter to open a dialog will also search your resource
file. <p>
Okay, let's have a look at InterslipControl-1.rsrc, our resource file.
The DLOG and accompanying DITL resource both have number 512. Since
ResEdit creates both with default ID=128 you should take care to
change the number on both. The dialog itself is pretty basic: four
buttons (connect, disconnect, update status and quit), two labels and
two status fields. <p>
<H2><A NAME="modal-dialog">An application with a modal dialog</A></H2>
Next, we will have to write the actual application. For this example,
we will use a modal dialog. This means that we will put up the dialog
and go into a loop asking the dialog manager for events (buttons
pushed). We handle the actions requested by the user until the quit
button is pressed, upon which we exit our loop (and the program). This
way of structuring your program is actually rather antisocial, since
you force the user to do whatever you, the application writer, happen
to want. A modal dialog leaves no way of escape whatsoever (except
command-option-escape), and is usually not a good way to structure
anything but the most simple questions. Even then: how often have you
been confronted with a dialog asking a question that you could not
answer because the data you needed was obscured by the dialog itself?
In the next example we will look at an application that does pretty
much the same as this one but in a more user-friendly way. <p>
On to the code itself, in file <A
HREF="example1/InterslipControl-1.py"> InterslipControl-1.py</A>. Have
a copy handy before you read on. The file starts off with a
textstring giving a short description. Not many tools do anything with
this as yet, but at some point in the future we <EM>will</EM> have all
sorts of nifty class browser that will display this string, so just
include it. Just put a short description at the start of each module,
class, method and function. After the initial description and some
comments, we import the modules we need. <p>
<A NAME="easydialogs"><CODE>EasyDialogs</CODE></A> is a handy standard
module that provides you with routines that put up common text-only
modal dialogs:
<UL>
<LI> <CODE>Message(str)</CODE>
displays the message "str" and an OK button,
<LI> <CODE>AskString(prompt, default)</CODE>
asks for a string, displays OK and Cancel buttons,
<LI> <CODE>AskYesNoCancel(question, default)</CODE>
displays a question and Yes, No and Cancel buttons.
</UL>
<A NAME="res"><CODE>Res</CODE></A> is a pretty complete interface to
the MacOS Resource Manager, described fully in Inside Mac. There is
currently no documentation of it, but the Apple documentation (or
Think Ref) will help you on your way if you remember two points:
<UL>
<LI> Resources are implemented as Python objects, and each routine
with a resource first argument is implemented as a python method.
<LI> When in doubt about the arguments examine the routines docstring,
as in <CODE>print Res.OpenResFile.__doc__</CODE>
</UL>
Similarly, <A NAME="dlg"><CODE>Dlg</CODE></A> is an interface to the
Dialog manager (with Dialogs being implemented as python objects and
routines with Dialog arguments being methods). The sys module you
know, I hope. <A NAME="interslip"><CODE>Interslip</CODE></A>,
finally, is the module with the interface to the InterSLIP driver. We
use four calls from it:
<UL>
<LI> <CODE>open()</CODE>
opens the driver
<LI> <CODE>connect()</CODE>
asks it to initiate a connection procedure (without waiting)
<LI> <CODE>disconnect()</CODE>
asks it to initiate a disconnection procedure (without waiting)
<LI> <CODE>status()</CODE>
returns the current connection status in the form of an integer state,
an integer "message sequence number" and a message string.
</UL>
Next in the source file we get definitions for our dialog resource
number and for the item numbers in our dialog. These should match the
situation in our resource file InterslipControl-1.rsrc,
obviously. Then we get an array converting numeric state codes
returned by <CODE>interslip.status()</CODE> to textual messages. <p>
On to the main program. We start off with opening our resource file,
which should live in the same folder as the python source. If we
cannot open it we use <CODE>EasyDialogs</CODE> to print a message and
exit. You can try it: just move the resource file somewhere else for a
moment. Then, we try to open the interslip driver, again catching an
error. All modules that raise <A NAME="macos-errors">MacOS error
exceptions</A> will pass a 2-tuple to the exception handler with the
first item being the numeric <CODE>OSErr</CODE> code and the second
one being an informative message. If no informative message is
available it will be the rather uninformative <CODE>"MacOS Error
-12345"</CODE>, but at least the second item will always be a
printable string. Finally we call do_dialog() to do the real work. <p>
<CODE>Do_dialog()</CODE> uses <CODE>Dlg.GetNewDialog()</CODE> to open
a dialog window initialized from 'DLOG' resource ID_MAIN and putting
it on screen in the frontmost position. Next, we go into a loop,
calling <CODE>Dlg.ModalDialog()</CODE> to wait for the next user
action. <CODE>ModalDialog()</CODE> will return us the item number that
the user has clicked on (or otherwise activated). It will handle a few
slightly more complicated things also, like the user typing into
simple textfields, but it will <EM>not</EM> do things like updating
the physical appearance of radio buttons, etc. See Inside Mac or
another programming guide for how to handle this
yourself. Fortunately, our simple application doesn't have to bother
with this, since buttons are the only active elements we have. So, we
do a simple switch on item number and call the appropriate routine to
implement the action requested. Upon the user pressing "quit" we
simply leave the loop and, hence, <CODE>do_dialog()</CODE>. This will
cause the python dialog object <CODE>my_dlg</CODE> to be deleted and
the on-screen dialog to disappear. <p>
<A NAME="dialog-warning">Time for a warning</A>: be very careful what
you do as long as a dialog is on-screen. Printing something, for
instance, may suddenly cause the standard output window to appear over
the dialog, and since we took no measures to redraw the dialog it will
become very difficult to get out of the dialog. Also, command-period
may or may not work in this situation. I have also seen crashes in
such a situation, probably due to the multiple event loops involved or
some oversight in the interpreter. You have been warned. <p>
The implementation of the "update status" command can use a bit more
explaining: we get the new information with <CODE>do_status()</CODE>
but now we have to update the on-screen dialog to present this
information to the user. The <CODE>GetDialogItem()</CODE> method of
the dialog returns three bits of information about the given item: its
type, its data handle and its rect (the on-screen <CODE>x,y,w,h</CODE>
coordinates). We are only interested in the data handle here, on which
we call <CODE>SetDialogItemText()</CODE> to set our new text. Note
here that python programmers need not bother with the C-string versus
pascal-string controversy: the python glue module knows what is needed
and converts the python string to the correct type. <p>
Finally, the three implementation routines <CODE>do_connect()</CODE>,
<CODE>do_disconnect()</CODE> and <CODE>do_status()</CODE> are simply
boring wrappers around the corresponding interslip methods that will
put up a dialog in case of an error. <p>
And that concludes our first example of the use of resources and
dialogs. Next, you could have a look at the source of EasyDialogs for
some examples of using input fields and filterprocs. Or, go on with
reading the <A HREF="example2.html">second part</A> of this document
to see how to implement a better version of this application. Not only
will it allow the user to go back to the finder (or other apps) when
your application is running, it will also free her of the RSI-inducing
chore of pressing "update status" continuously... <p>

180
Mac/Demo/example2.html Normal file
View File

@ -0,0 +1,180 @@
<HTML><HEAD><TITLE>Using python to create Macintosh applications, part two</TITLE></HEAD>
<BODY>
<H1>Using python to create Macintosh applications, part two</H1>
<HR>
In this document we rewrite the application of the <A
HREF="example1.html">previous example</A> to use modeless dialogs. We
will use an application framework, and we will have a look at creating
applets, standalone applications written in Python. <A
HREF="example2/InterslipControl-2.py">Source</A> and resource file (in
binary and <A HREF="example2/InterslipControl-2.rsrc.hqx">BinHex</A>
form for downloading) are available in the folder <A
HREF="example2">example2</A>. If you want to run the program on your
machine you will also need a new copy of <A
HREF="update-to-1.3/FrameWork.py">FrameWork.py</A>, which has been
updated since the 1.3 release. <p>
Again, we start with ResEdit to create our dialogs. Not only do we
want a main dialog this time but also an "About" dialog, and we
provide the <A NAME="bundle">BNDL resource</A> and related stuff that
an application cannot be without. (Actually, a python applet can be
without, <A HREF="#no-bundle">see below</A>). "Inside Mac" or various
books on macintosh programming will help here. Also, you can refer to
the resource files provided in the Python source distribution for some
of the python-specific points of BNDL programming: the
"appletbundle.rsrc" file is what is used for creating applets if you
don't provide your own resource file. <p>
Let's have a look at InterslipControl-2.rsrc, our resource file. First
off, there's the standard BNDL combo. I've picked 'PYTi' as signature
for the application. I tend to pick PYT plus one lower-case letter for
my signatures. The finder gets confused if you have two applications
with the same signature. This may be due to some incorrectness on the
side of "mkapplet", I am not sure. There is one case when you
definitely need a unique signature: when you create an applet that has
its own data files and you want the user to be able to start your
applet by double-clicking one of the datafiles. <p>
There's little to tell about the BNDL stuff: I basically copied the
generic Python applet icons and pasted in the symbol for
InterSLIP. The two dialogs are equally unexciting: dialog 512 is our
main window which has four static text fields (two of which we will be
modifying during runtime, to show the status of the connection) and
two buttons "connect" and "disconnect". The "quit" and "update status"
buttons have disappeared, because they are handled by a menu choice
and automatically, respectively. <p>
<H2>A modeless dialog application using FrameWork</H2>
On to the source code in <A
HREF="example2/InterslipControl-2.py">InterslipControl-2.py</A>. The
start is similar to our previous example program <A
HREF="example1/InterslipControl-1.py">InterSlipControl-1.py</A>, with
one extra module being imported. To make life more simple we will use
the <CODE>FrameWork</CODE> module, a nifty piece of code that handles
all the gory mac details of event loop programming, menubar
installation and all the other code that is the same for every mac
program in the world. Like most standard modules, FrameWork will run
some sample test code when you invoke it as a main program, so try it
now. It will create a menu bar with an Apple menu with the about box
and a "File" menu with some pythonesque choices (which do nothing
interesting, by the way) and a "Quit" command that works. <p>
<CITE>
A more complete description of <A NAME="framework">FrameWork</A> is
sorely needed, and will (at some point) be incorporated in the
programmers manual or in place of this paragraph. For now you'll have
to make do with the knowledge that you use FrameWork by building your
classes upon the classes provided by it and selectively overriding
methods to extend its functionality (or override the default
behaviour). And you should read the Source, of Course:-) <p>
</CITE>
After the imports we get the definitions of resource-IDs in our
resource file, slightly changed from the previous version of our
program, and the state to string mapping. The main program is also
similar to our previous version, with one important exception: we
first check to see whether our resource is available before opening
the resource file. Why is this? Because later, when we will have
converted the script to an applet, our resources will be available in
the applet file and we don't need the separate resource file
anymore. <p>
Next comes the definition of our main class,
<CODE>InterslipControl</CODE>, which inherits
<CODE>FrameWork.Application</CODE>. The Application class handles the
menu bar and the main event loop and event dispatching. In the
<CODE>__init__</CODE> routine we first let the base class initialize
itself, then we create our modeless dialog and finally we jump into
the main loop. The main loop continues until <CODE>self</CODE> is
raised, which we will do when the user selects "quit". When we create
the instance of <CODE>MyDialog</CODE> (which inherits
<CODE>DialogWindow</CODE>, which inherits <CODE>Window</CODE>) we pass
a reference to the application object, this reference is used to tell
Application about our new window. This enables the event loop to keep
track of all windows and dispatch things like update events and mouse
clicks. <p>
The <CODE>makeusermenus()</CODE> method (which is called sometime
during the Application <CODE>__init__</CODE> routine) creates a File
menu with a Quit command (shortcut command-Q), which will callback to
our quit() method. <CODE>Quit()</CODE>, in turn, raises 'self' which
causes the mainloop to terminate. <p>
Application provides a standard about box, but we override this by
providing our own <CODE>do_about()</CODE> method which shows an about
box from a resource as a modal dialog. This piece of code should look
familiar to you from the previous example program. That do_about is
called when the user selects About from the Apple menu is, again,
taken care of by the __init__ routine of Application. <p>
Our main object finally overrides <CODE>idle()</CODE>, the method
called when no event is available. It passes the call on to our dialog
object to give it a chance to update the status fields, if needed. <p>
The <CODE>MyDialog</CODE> class is the container for our main
window. Initialization is again done by first calling the base class
<CODE>__init__</CODE> function and finally setting two local variables
that are used by <CODE>updatestatus()</CODE> later. <p>
<CODE>Do_itemhit()</CODE> is called when an item is selected in this
dialog by the user. We are passed the item number (and the original
event structure, which we normally ignore). The code is similar to the
main loop of our previous example program: a switch depending on the
item selected. <CODE>Connect()</CODE> and <CODE>disconnect()</CODE>
are again quite similar to our previous example. <p>
<CODE>Updatestatus()</CODE> is different, however. It is now
potentially called many times per second instead of only when the
user presses a button we don't want to update the display every time
since that would cause some quite horrible flashing. Luckily,
<CODE>interslip.status()</CODE> not only provides us with a state and
a message but also with a message sequence number. If neither state
nor message sequence number has changed since the last call there is
no need to update the display, so we just return. For the rest,
nothing has changed. <p>
<H2><IMG SRC="html.icons/mkapplet.gif"><A NAME="applets">Creating applets</A></H2>
Now, if you have a PowerPC Macintosh, let us try to turn the python
script into an applet, a standalone application. Actually,
"standalone" is probably not the correct term here, since an applet
does still depend on a lot of the python environment: the PythonCore
shared library, the Python Preferences file, the python Lib folder and
any other modules that the main module depends on. It is possible to
get rid of all these dependencies except for the dependency on
PythonCore, but at the moment that is still quite difficult so we will
ignore that possibility for now. By standalone we mean here that the
script has the look-and-feel of an application, including the ability
to have its own document types, be droppable, etc. <p>
The easiest way to create an applet is to take your source file and
drop it onto "mkapplet" (normally located in the Python home
folder). This will create an applet with the same name as your python
source with the ".py" stripped. Also, if a resource file with the same
name as your source but with ".rsrc" extension is available the
resources from that file will be copied to your applet too. If there
is no resource file for your script a set of default resources will be
used, and the applet will have the default creator 'PYTa'. The latter
also happens if you do have a resource file but without the BNDL
combo. <A NAME="no-bundle">Actually</A>, for our example that would
have been the most logical solution, since our applet does not have
its own data files. It would have saved us hunting for an unused
creator code. The only reason for using the BNDL in this case is
having the custom icon, but that could have been done by pasting an
icon on the finder Info window, or by providing an custon icon in your
resource file and setting the "custom icon" finder bit. <p>
If you need slightly more control over the mkapplet process you can
double-click mkapplet, and you will get dialogs for source and
destination of the applet. The rest of the process, including locating
the resource file, remains the same. <p>
Note that though our example application completely bypasses the
normal python user interface this is by no means necessary. Any python
script can be turned into an applet, and all the usual features of the
interpreter still work. <p>
That's all for this example, you may now return to the <A HREF="index.html">
table of contents</A> to pick another topic. <p>

94
Mac/Demo/index.html Normal file
View File

@ -0,0 +1,94 @@
<HTML><HEAD><TITLE>Macintosh Python crash course</TITLE></HEAD>
<BODY>
<H1><IMG SRC="html.icons/python.gif">Macintosh Python crash course</H1>
<HR>
This set of documents provides an introduction to various aspects of
Python programming on the Mac. It is assumed that the reader is
already familiar with Python and, to some extent, with MacOS Toolbox
programming. Other readers may find something interesting here too,
your mileage may vary. <p>
Another set of Macintosh-savvy examples, more aimed at beginners, is
maintained by Joseph Strout, at <A
HREF="http://www-acs.ucsd.edu/~jstrout/python/">
http://www-acs.ucsd.edu/~jstrout/python/</A>.
<P>
The document was actually written while I was working on a "real"
project: creating a single-button application that will allow my
girlfriend to read her mail (which actually pass thry <EM>my</EM>
mailbox, so I get to read it too, but don't tell her:-) without her
having to worry about internet connections, unix commands, etc. The
application, when finished, will connect to the net using InterSLIP,
start a (pseudo-)POP server on unix using rsh and use AppleScript to
tell Eudora to connect to that server and retrieve messages. <p>
<CITE>
If you want to try the examples here you will have to download some
fixes to the 1.3 distribution to your Macintosh. You need an updated
version of <A HREF="update-to-1.3/FrameWork.py">FrameWork.py</A> (to
go in <CODE>Lib:mac</CODE> and updated <A
HREF="update-to-1.3/into-PlugIns.hqx">project templates</A> to go into
the <CODE>PlugIns</CODE> folder for PPC users.
Users of 1.3.1 or later distributions don't need these fixes.<P>
</CITE>
If you are reading this document on the web and would prefer to read
it offline you can transfer the whole stuff (as a BinHexed StuffIt
archive) from <A HREF="complete.hqx"> here</A>. This archive includes
the fixes mentioned in the previous paragraph. <p>
<H2>Table of contents</H2>
<UL>
<LI>
<A HREF="example1.html">Using python to create Macintosh applications,
part one</A> explains how to create a simple modal-dialog application
in Python. It also takes a glance at using the toolbox modules Res and
Dlg, and EasyDialogs for simple question-dialogs.
<LI>
<A HREF="example2.html">Using python to create Macintosh applications,
part two</A> turns the previous example program into a more complete
mac application, using a modeless dialog, menus, etc. It also explains
how to create applets, standalone applications written in Python.
<LI>
In the Python distribution two more examples are included without
explanation. <I>PICTbrowse</I> is an application that locates PICT
resources and displays them, it demonstrates some quickdraw and the
resource and list namagers. <I>Imgbrowse</I> displays image files in
many different formats (gif, tiff, pbm, etc). It shows how to use the
img modules on the mac.
<LI>
<A HREF="plugins.html">Creating a C extension module on the Macintosh</A>
is meant for the hardcore programmer, and shows how to create an
extension module in C. It also handles using Modulator to create the
boilerplate for your module, and creating dynamically-loadable modules
on PowerPC Macs.
<LI>
<A HREF="applescript.html">Using Open Scripting Architecture from Python</A> explains
how to create a Python module interfacing to a scriptable application,
and how to use that module in your python program.
</UL>
At some point in the (possibly distant) future, I will add chapters on
how to use bgen to create modules completely automatic and how to make
your Python program scriptable, but that will have to wait. <p>
<HR>
Please let me know if you miss critical information in this
document. I am quite sure that I will never find the time to turn it
into a complete MacPython programmers guide (which would probably be a
400-page book instead of 5 lousy html-files), but it should contain
at least the information that is neither in the standard Python
documentation nor in Inside Mac or other Mac programmers
documentation. <p>
<HR>
<A HREF="http://www.cwi.nl/~jack">Jack Jansen</A>,
<A HREF="mailto:jack@cwi.nl">jack@cwi.nl</A>, 6-Mar-1996.

363
Mac/Demo/plugins.html Normal file
View File

@ -0,0 +1,363 @@
<HTML><HEAD><TITLE>Creating a C extension module on the Macintosh</TITLE></HEAD>
<BODY>
<H1>Creating a C extension module on the Macintosh</H1>
<HR>
This document gives a step-by-step example of how to create a new C
extension module on the mac. For this example, we will create a module
to interface to the programmers' API of InterSLIP, a package that
allows you to use MacTCP (and, hence, all internet services) over a
modem connection. <p>
<H2>Prerequisites</H2>
There are a few things you need to pull this off. First and foremost,
you need a C development environment. Actually, you need a specific
development environment, CodeWarrior by <A
HREF="http://www.metrowerks.com/">MetroWerks</A>. You will probably
need the latest version. You may be able to get by with an older
version of CodeWarrior or with another development environment (Up to
about 1994 python was developed with THINK C, and in the dim past it
was compiled with MPW C) assuming you have managed to get Python to
compile under your development environment, but the step-by-step
character of this document will be lost. <p>
Next, you need a <A HREF="http://www.python.org/python/Sources.html">python
source distribution</A>. There is a <A
HREF="update-to-1.3/into-PlugIns.hqx"> fixed project template</A> that
you also need if you are going to make a dynamically loaded
module. For PowerPC development you can actually get by without a full
source distribution, using the PPC Development distribution (if I have
gotten around to putting it together by the time you read
this). You'll also need a functional python interpreter, and the
Modulator program (which lives in <CODE>Tools:Modulator</CODE> in the
standard source distribution). You may also find that Guido's <A
HREF="http://www.python.org/doc/ext/ext.html">Extending and embedding
the Python interpreter</A> is a very handy piece of documentation. I
will skip lots of details that are handled there, like complete
descriptions of <CODE>Py_ParseTuple</CODE> and such utility routines,
or the general structure of extension modules. <p>
<H2>InterSLIP and the C API to it</H2>
InterSLIP, the utility to which we are going to create a python
interface, is a system extension that does all the work of connecting
to the internet over a modem connection. InterSLIP is provided
free-of-charge by <A
HREF="http://www.intercon.com/">InterCon</A>. First it connects to
your modem, then it goes through the whole process of dialling,
logging in and possibly starting the SLIP software on the remote
computer and finally it starts with the real work: packing up IP
packets handed to it by MacTCP and sending them to the remote side
(and, of course, the reverse action of receiving incoming packets,
unpacking them and handing them to MacTCP). InterSLIP is a device
driver, and you control it using a application supplied with it,
InterSLIP Setup. The API that InterSLIP Setup uses to talk to the
device driver is published in the documentation and, hence, also
useable by other applications. <p>
I happened to have a C interface to the API, which is all ugly
low-level device-driver calls by itself. The C interface is in <A
HREF="interslip/InterslipLib.c">InterslipLib.c</A> and <A
HREF="interslip/InterslipLib.h">InterslipLib.h</A>, we'll
concentrate here on how to build the Python wrapper module around
it. Note that this is the "normal" situation when you are writing a
Python extension module: you have some sort of functionality available
to C programmers and want to make a Python interface to it. <p>
<H2>Using Modulator</H2>
The method we describe in this document, using Modulator, is the best
method for small interfaces. For large interfaces there is another
tool, Bgen, which actually generates the complete module without you
lifting a single finger. Bgen, however, has the disadvantage of having
a very steep learning curve, so an example using it will have to wait
until another document, when I have more time. <p>
First, let us look at the <A
HREF="interslip/InterslipLib.h">InterslipLib.h</A> header file,
and see that the whole interface consists of six routines:
<CODE>is_open</CODE>, <CODE>is_connect</CODE>,
<CODE>is_disconnect</CODE>, <CODE>is_status</CODE>,
<CODE>is_getconfig</CODE> and <CODE>is_setconfig</CODE>. Our first
step will be to create a skeleton file <A
HREF="interslip/@interslipmodule.c">@interslipmodule.c</A>, a
dummy module that will contain all the glue code that python expects
of an extension module. Creating this glue code is a breeze with
modulator, a tool that we only have to tell that we want to create a
module with methods of the six names above and that will create the
complete skeleton C code for us. <p>
Why call this dummy module <CODE>@interslipmodule.c</CODE> and not
<CODE>interslipmodule.c</CODE>? Self-preservation: if ever you happen
to repeat the whole process after you have actually turned the
skeleton module into a real module you would overwrite your
hand-written code. By calling the dummy module a different name you
have to make <EM>two</EM> mistakes in a row before you do this. <p>
On systems with the Tk windowing API for Python (currently only
unix/X11 systems, but mac support may be available when you read this)
this is extremely simple. It is actually so simple that it pays to
create the skeleton module under unix and ship the code to your
mac. You start modulator and are provided with a form in which you
fill out the details of the module you are creating. <p>
<IMG SRC="html.icons/modulator.gif" ALIGN=CENTER><p>
You'll need to supply a module name (<CODE>interslip</CODE>, in our
case), a module abbreviation (<CODE>pyis</CODE>, which is used as a
prefix to all the routines and data structures modulator will create
for you) and you enter the names of all the methods your module will
export (the list above, with <CODE>is_</CODE> stripped off). Note that
we use <CODE>pyis</CODE> as the prefix instead of the more logical
<CODE>is</CODE>, since the latter would cause our routine names to
collide with those in the API we are interfacing to! The method names
are the names as seen by the python program, and the C routine names
will have the prefix and an underscore prepended. Modulator can do
much more, like generating code for objects and such, but that is a
topic for a later example. <p>
Once you have told modulator all about the module you want to create
you press "check", which checks that you haven't omitted any
information and "Generate code". This will prompt you for a C output
file and generate your module for you. <p>
<H2>Using Modulator without Tk</H2>
Modulator actually uses a two-stage process to create your code: first
the information you provided is turned into a number of python
statements and then these statements are executed to generate your
code. This is done so that you can even use modulator if you don't
have Tk support in Python: you'll just have to write the modulator
python statements by hand (about 10 lines, in our example) and
modulator will generate the C code (about 150 lines, in our
example). Here is the Python code you'll want to execute to generate
our skeleton module: <p>
<CODE><PRE>
import addpack
addpack.addpack('Tools')
addpack.addpack('modulator')
import genmodule
m = genmodule.module()
m.name = 'interslip'
m.abbrev = 'pyis'
m.methodlist = ['open', 'connect', 'disconnect', 'status', \
'getconfig', 'setconfig']
m.objects = []
fp = open('@interslipmodule.c', 'w')
genmodule.write(fp, m)
</PRE></CODE>
Drop this program on the python interpreter and out will come your
skeleton module. <p>
Now, rename the file to interslipmodule.c and you're all set to start
developing. The module is complete in the sense that it should
compile, and that if you import it in a python program you will see
all the methods. It is, of course, not yet complete in a functional
way... <p>
<H2>Adding a module to 68K Python</H2>
What you do now depends on whether you're developing for PowerPC (or
for CFM68K) or for "traditional" mac. For a traditional 68K Python,
you will have to add your new module to the project file of the Python
interpreter, and you have to edit "config.c" to add the module to the
set of builtin modules. In config.c you will add the module at two
places: near the start of the file there is a list of external
declarations for all init() routines. Add a line of the form
<CODE><PRE>
extern void initinterslip();
</PRE></CODE>
here. Further down the file there is an array that is initialized with
modulename/initfunction pairs. Add a line of the form
<CODE><PRE>
{"interslip", initinterslip},
</PRE></CODE>
here. You may want to bracket these two lines with
<CODE><PRE>
#ifdef USE_INTERSLIP
#endif
</PRE></CODE>
lines, that way you can easily control whether the module is
incorporated into python at compile time. If you decide to do the
latter edit your config file (you can find the name in the "C/C++
language" section of the MW preferences dialog, it will probably be
"mwerks_nonshared_config.h") and add a
<CODE><PRE>
#define USE_INTERSLIP
</PRE></CODE>
Make the new interpreter and check that you can import the module, see
the methods (with "dir(interslip)") and call them. <p>
<H2>Creating a PowerPC plugin module</H2>
For PowerPC development you could follow the same path, but it is
actually a better idea to use a dynamically loadable module. The
advantage of dynamically loadable modules is that they are not loaded
until a python program actually uses them (resulting in less memory
usage by the interpreter) and that development is a lot simpler (since
your projects will all be smaller). Moreover, you can distribute a
plugin module by itself without haveing to distribute a complete
python interpreter. <p>
Go to the "PlugIns" folder and copy the files xxmodule.µ,
xxmodule_config.h and xxmodule.µ.exp to interslipmodule.µ,
interslipmodule_config.h and interslipmodule.µ.exp, respectively. Edit
interslipmodule.µ.exp and change the name of the exported routine
"initxx" to "initinterslip". Open interslipmodule.µ with CodeWarrior,
remove the file xxmodule.c and add interslipmodule.c and make a number
of adjustments to the preferences:
<UL>
<LI> in C/C++ language, set the header file to interslipmodule_config.h
<LI> in PPC linker, set the entry point to "initinterslip"
<LI> in PPC PEF, set the fragment name to "interslipmodule"
<LI> in PPC Project, set the output file name to "interslipmodule.slb".
</UL>
Next, compile and link your module, fire up python and do the same
tests as for 68K python. <p>
<H2>Getting the module to do real work</H2>
So far, so good. In half an hour or so we have created a complete new
extension module for Python. The downside, however, is that the module
does not do anything useful. So, in the next half hour we will turn
our beautiful skeleton module into something that is at least as
beautiful but also gets some serious work done. For this once,
<EM>I</EM> have spent that half hour for you, and you can see the
results in <A
HREF="interslip/interslipmodule.c">interslipmodule.c</A>. <p>
We add
<CODE><PRE>
#include "InterslipLib.h"
#include "macglue.h"
</PRE></CODE>
to the top of the file, and work our way through each of the methods
to add the functionality needed. Starting with open, we fill in the
template docstring, the value accessible from Python by looking at
<CODE>interslip.open.__doc__</CODE>. There are not many tools using
this information at the moment, but as soon as class browsers for
python become available having this minimal documentation available is
a good idea. We put "Load the interslip driver" as the comment
here. <p>
Next, we tackle the body of <CODE>pyis_open()</CODE>. Since it has no
arguments and no return value we don't need to mess with that, we just
have to add a call to <CODE>is_open()</CODE> and check the return for
an error code, in which case we raise an error:
<CODE><PRE>
err = is_open();
if ( err ) {
PyErr_Mac(ErrorObject, err);
return NULL;
}
</PRE></CODE>
The routine <CODE><A NAME="PyErr_Mac">PyErr_Mac()</A></CODE> is a
useful routine that raises the exception passed as its first
argument. The data passed with the exception is based on the standard
MacOS error code given, and PyErr_Mac() attempts to locate a textual
description of the error code (which sure beats the "error -14021"
messages that so many macintosh applications tell their poor
users). <p>
We will skip pyis_connect and pyis_disconnect here, which are pretty
much identical to pyis_open: no arguments, no return value, just a
call and an error check. With pyis_status() things get interesting
again: this call still takes 3 arguments, and all happen to be values
returned (a numeric connection status indicator, a message sequence
number and a pointer to the message itself, in MacOS pascal-style
string form). We declare variables to receive the returned values, do
the call, check the error and format the return value. <p>
Building the return value is done using <CODE><A
NAME="Py_BuildValue">Py_BuildValue</A></CODE>:
<CODE><PRE>
return Py_BuildValue("iiO&", (int)status, (int)seqnum, PyMac_BuildStr255, message);
</PRE></CODE>
Py_BuildValue() is a very handy routine that builds tuples according
to a format string, somewhat similar to the way <CODE>printf()</CODE>
works. The format string specifies the arguments expected after the
string, and turns them from C objects into python objects. The
resulting objects are put in a python tuple object and returned. The
"i" format specifier signifies an "int" (hence the cast: status and
seqnum are declared as "long", which is what the is_status() routine
wants, and even though we use a 4-byte project there is really no
reason not to put the cast here). Py_BuildValue and its counterpart
Py_ParseTuple have format codes for all the common C types like ints,
shorts, C-strings, floats, etc. Also, there is a nifty escape
mechanism to format values about which is does not know. This is
invoked by the "O&" format: it expects two arguments, a routine
pointer and an int-sized data object. The routine is called with the
object as a parameter and it should return a python objects
representing the data. <CODE>Macglue.h</CODE> declares a number of
such formatting routines for common MacOS objects like Str255, FSSpec,
OSType, Rect, etc. See the comments in the include file for
details. <p>
<CODE>Pyis_getconfig()</CODE> is again similar to pyis_getstatus, only
two minor points are worth noting here. First, the C API return the
input and output baudrate squashed together into a single 4-byte
long. We separate them out before returning the result to
python. Second, whereas the status call returned us a pointer to a
<CODE>Str255</CODE> it kept we are responsible for allocating the
<CODE>Str255</CODE> for getconfig. This is something that would have
been easy to get wrong had we not used prototypes everywhere. Morale:
always try to include the header files for interfaces to libraries and
other stuff, so that the compiler can catch any mistakes you make. <p>
<CODE>Pyis_setconfig()</CODE> finally shows off
<CODE>Py_ParseTuple</CODE>, the companion function to
<CODE>Py_BuildValue</CODE>. You pass it the argument tuple "args"
that your method gets as its second argument, a format string and
pointers to where you want the arguments stored. Again, standard C
types such as strings and integers Py_ParseTuple knows all about and
through the "O&" format you can extend the functionality. For each
"O&" you pass a function pointer and a pointer to a data area. The
function will be called with a PyObject pointer and your data pointer
and it should convert the python object to the correct C type. It
should return 1 on success and 0 on failure. Again, a number of
converters for standard MacOS types are provided, and declared in
<CODE>macglue.h</CODE>. <p>
Next in our source file comes the method table for our module, which
has been generated by modulator (and it did a good job too!), but
which is worth looking at for a moment. Entries are of the form
<CODE><PRE>
{"open", pyis_open, 1, pyis_open__doc__},
</PRE></CODE>
where the entries are python method name, C routine pointer, flags and
docstring pointer. The value to note is the 1 for the flags: this
signifies that you want to use "new-style" Py_ParseTuple behaviour. If
you are writing a new module always use this, but if you are modifying
old code which calls something like <CODE>getargs(args, "(ii)",
...)</CODE> you will have to put zero here. See "extending and
embedding" or possibly the getargs.c source file for details if you
need them. <p>
Finally, we add some code to the init module, to put some symbolic
constants (codes that can by returned by the status method) in the
module dictionary, so the python program can use "interslip.RUN"
instead of the cryptic "4" when it wants to check that the interslip
driver is in RUN state. Modulator has already generated code to get at
the module dictionary using PyModule_GetDict() to store the exception
object, so we simply call
<CODE><PRE>
PyDict_SetItemString(d, "IDLE", PyInt_FromLong(IS_IDLE));
</PRE></CODE>
for each of our items. Since the last bit of code in our init routine
checks for previous errors with <CODE>PyErr_Occurred()</CODE> and
since <CODE>PyDict_SetItemString()</CODE> gracefully handles the case
of <CODE>NULL</CODE> parameters (if <CODE>PyInt_FromLong()</CODE>
failed, for instance) we don't have to do error checking here. In some
other cases you may have to do error checking yourself. <p>
This concludes our crash-course on writing Python extensions in C on
the Macintosh. If you are not done reading yet I suggest you look
back at the <A HREF="index.html">MacPython Crashcourse index</A> to
find another topic to study. <p>