from Tkinter import * class Question: def __init__(self, master): self.button = Button(master, bitmap='question', command=self.action) self.button.pack() def action(self): # Toggle image if self.button['bitmap'] == 'question': self.button.config(bitmap='questhead') else: self.button.config(bitmap='question')It should be stressed that there is nothing Grail-specific in this example module: in fact, it can be run as a stand-alone Python program (while still remaining usable as an applet module) by adding the following lines at the end:
if __name__ == '__main__': # Are we run as a main program? root = Tk() button = Question(root) root.mainloop()This applet can be invoked by the following HTML page (file test.html):
<HEAD> <TITLE>Grail Applet Test Page</TITLE> </HEAD> <BODY> <H1>Test an Applet Here!</H1> Click this button! <OBJECT CLASSID="Question.py"> If you see this, your browser does not support Python applets. </OBJECT> </BODY>Grail must be able to find the Question class in the module file Question.py. By default, the module name and the class name are assumed to be the same.
The <OBJECT> tag supports "alternative contents". Browsers that don't understand this tag will skip the tags, and display whatever there is between <OBJECT> and </OBJECT>. In the example, this is the string "If you see this, your browser does not support Python applets.". Browsers that do understand it will interpret it, and skip (nearly) everything they encounter until the matching end tag. Thus, it is possible to have an applet displayed by Grail but an image displayed by other browsers.
The <OBJECT> tag is currently being proposed by the W3C, in a W3C working draft entitled Inserting objects into HTML (edited by Dave Raggett). As far as we know, Grail is the first browser to implement this tag. Because of its tentative status, it is possible that details will change in a future release of Grail.
The <OBJECT> element has the following general format:
<OBJECT CLASSID="string" CODEBASE="string" MENU="string" WIDTH=number HEIGHT=number> <PARAM NAME=name VALUE="string"> ...more <PARAM> tags... ...alternative contents... </OBJECT>
The proposed <OBJECT> tag has additional attributes TYPE, NAME, DECLARE, DATA, ALIGN, VSPACE and HSPACE, which are currently ignored by Grail. The MENU attribute is a Grail specific extension.
Attribute and tag names are case-insensitive; however their values are case-sensitive. Values containing other characters than letters or digits should be enclosed in double quotes. (For the exact rules, consult any HTML manual.) All attributes except CLASS are optional. Attributes can be specified in any order. Each attribute name should be specified at most once.
The <OBJECT> tag describes the code object to be executed. The <PARAM> elements provides runtime parameters to the code object. The contents of the <OBJECT> el ement, with the exception of <PARAM> elements, form the "alternative contents" mentioned above. The alternative contents are only displayed when the attributes of the <OBJECT> tag indicate (without having to do any network traffic) that it is not a Python applet.
The CLASSID and CODEBASE attributes together determine the class and module for the applet. The following combinations are allowed:
Other combinations do not specify a Python applet; the alternative contents of the <OBJECT> element will be displayed.
The class is instantiated with a first argument giving its "master" widget. This is a Tkinter Frame instance specifically created for this applet; its parent is the viewer's text widget. If the WIDTH and/or HEIGHT attributes were present, the frame is given this initial width and/or height.
When the MENU attribute is present, instead of a frame inside the viewer's text widget, the widget's master is a menu in the browser's menu bar with the given name. The menu remains in existence as long as the document that invoked the applet remains visible in the viewer. The widget instance is expected to add entries to this menu that provide commands specific to the document. (In this case, the widget has no "real estate" of its own.)
The name/value pairs specified by the <PARAM> elements are passed as keyword arguments to the constructor of the applet class, with their names converted to lower case. For example, an applet whose class begins as follows:
class Animation: def __init__(self, master, image="grail", rate=10): ...could be invoked by any of the following element groups:
<OBJECT CLASSID==Animation.py> </OBJECT> <OBJECT CLASSID==Animation.py> <PARAM NAME=IMAGE VALUE=python> </OBJECT> <OBJECT CLASSID==Animation.py> <PARAM NAME=RATE VALUE=4> </OBJECT> <OBJECT CLASSID==Animation.py> <PARAM NAME=IMAGE VALUE=spam> <PARAM NAME=RATE VALUE=30> </OBJECT>
Value strings that look like numbers are automatically converted to Python numeric values of the appropriate type (int, long or float); all other values are passed as strings.
Two additional, indispensable sources of information are the Tk manual pages and the Tkinter source. Ousterhout's Tcl/Tk book is of limited value, since it refers to the manual pages for most details.
.remember_url(url [, title])
(title, timestamp)representing the information in the global history about the given URL. If the URL is not known, return
Browserobject may be found in The
BrowserObject Interface in Grail.
Viewerobject containing the applet. A description of the
Viewerobject may be found in The
ViewerObject Interface in Grail.
To create a page that works with Grail, with Java compatible browsers (like Netscape 2.0), as well as with other browsers, you can use nested alternative contents: at the outermost level, you have an <OBJECT> tag (for Grail), inside it an <APPLET> tag (for Netscape 2.0), and inside that an IMG tag (for other browsers), for instance:
<!--Rotating earth Python applet--> <OBJECT CLASSID=earth.py> <!--Rotating earth Java applet--> <APPLET CODE=earth.class> <!--Static earth image--> <IMG SRC="earth.gif"> </APPLET> </OBJECT>
Grail has the capability of having support for new protocols added in the form of a plug-in architecture. The specifics of how this is accomplished is reviewed in the Extending Grail section. This section is worth while reading since applet local protocol handlers are most easily adapted from working plugins.
An applet may wish to override the behavior of the default plug-in protocol handlers for protocol accesses via anchors on the same page (e.g. with the same Context) as the applet. Specifically, an applet may wish to provide interaction with the handler at various stages of resolution and retreval. An applet may wish to override the default error handling. In the example below, a local implementation of the hdlAPI (CNRI Handle System API), the local handler retrieves the handle data from a alternate source if the handle is not found. This type of interaction with the protocol handler is only possible with a locally installed handler.
Local protocol handlers are installed from an applet and are subject to the restrictions of the applets restricted execution environment. This presents some minor porting issues when moving a plug-in API to a local handler.
Once you have a working plug-in protocol handler, porting it to be installed by an applet is fairly straightforward. One method of reusing 'most' of a plug-in protocol handler without copying code, is to try and use the plug-in version of the handler as is, and where it fails (either because of restricted execution environment or because the functionality is lacking), override the effected methods by subclassing.
Here is some excerpts from an example local protocol handler for communication and retrevals from the CNRI handle system. This handler only deals with handles that point to a specific type of data, making the class much simpler than the parent from which it derives.
import hdlAPI import nullAPI import hdllib import hdl_type_nlm # custom type handlers are imported rather # than installed in $home/.grail/protocols HDL_API_NAME = 'hdlAPI' # Key used to install the handler class MEDLINE_hdl_access(hdlAPI.hdl_access): def __init__(self, hdl, method, params): """The init is usually simpler than the generic handler""" nullAPI.null_access.__init__(self, hdl, method, params) self._hdl, self._attrs = hdlAPI.parse_handle(hdl) self._types = hdl_type_nlm.handle_types self._formatter = hdl_type_nlm.data_formatter def pollmeta(self): """This is where you would intercept the various error conditions occuring during resolution""" nullAPI.null_access.pollmeta(self) try: replyflags, self._items = self._hashtable.get_data( self._hdl, self._types) except hdllib.Error, inst: if inst.err == hdllib.HP_HANDLE_NOT_FOUND: # Do some local processing here to fix the error # Or, if all else fails... raise IOError, "handle not found"
Once the custom handler class is complete, you need to associate it with the page by using the set_local_api API available from grail_context on the applets root object. The first arguement is a string that is used as a key for finding the handler. The convention that grail uses is: lowercase scheme id + 'API'. So for http, the first argument would be 'httpAPI'. The second argument is the class for the local handler. This class is expected to contain interfaces for Grail's protocol API as defined in the plug-in manual. Here's an example local handler for the CNRI handle system protocol:
import MEDLINEhdlAPI class Controller: def __init__(self, root): # Install the local protocol handler root.grail_context.set_local_api(MEDLINEhdlAPI.HDL_API_NAME, MEDLINEhdlAPI.MEDLINE_hdl_access)
This is assumming the applet is envoked on class 'Controller'.
The applet will install the local handler for the context of the current page. As soon as a link is followed, all local handlers are deleted from the current context. This is to prevent 'secret' local handlers from being perminantly installed from an applet.