NOTE:
This project is no longer being maintained: it was developed for my masters thesis, which was completed in early 1997. I still, however, welcome any questions or comments that people may have.

[Home] [ToC] [Prev] [Next]


iHTML Python Language

Embedded Program Tutorial

An embedded program is essentially one that appears as an interactive applet, which the user sees as part of an HTML document. The new HTML tag <OBJECT> is used to insert these programs into an HTML document, as described in the introduction.

All embedded Python programs must export at least one symbol, ihEmbed, which is the program's main entry point. This symbol should be a callable object, which is given a variable number of arguments and returns an instance of an Application class.

Example 1: The Simplest Embedded Program

The simplest embedded program possible is:

# Get standard application class definitions.
import ihApp

# Our own application is a subclass of the iHTML-defined
# Application class.
class ihEmbed(ihApp.Application):

    # Class initialization.
    def __init__(self,**args):

        # Just send control on up to the superclass.
        apply(ihApp.Application.__init__,(self,),args)

# Make our program externally visible.
__export__ = ['ihEmbed']

This class does nothing -- it just passes any arguments it finds on up to the top-level Application class; it does no drawing or other operations, so all the user will see is an empty area where the program appears in the document. The HTML markup we use to embed this could be:

<OBJECT CLASSID="embed_simple.ipy">
</OBJECT>

If you have an iHTML-capable browser, you can take a look at the simple example; note, however, that this one is not extremely exciting.

Example 2: Drawing Graphics

This example actually does something useful - it draws a static picture. This requires that it determine the size of the area it will use, then draw the actual graphic. The former is accomplished by calling the Widget class's (of which the Application class is a subclass) methods Dimensions() and TextExtent(). The Widget class also supplies many functions for rendering, including color allocation, setting pen colors, drawing lines or rectangles, etc.

An embedded applet should render itself when it receives a Redraw event; this can be accomplished by defining an OnEvent() method, which draws the applet.

# Displaying graphics.

# Get the standard modules we need:
import ihApp    # Interactive HTML application interface

# Our own application is a subclass of the iHTML-defined
# Application class.
class ihEmbed(ihApp.Application):

    # Class initialization.
    def __init__(self,**args):

	# Let superclass initialize itself.  This takes care of
	# interpreting the Width and Height parameters.
	apply(ihApp.Application.__init__,(self,),args)

	# Extract widget dimensions, as set by superclass.
	x, y, width, height = self.Dimensions()

	# Find font dimensions and use to help compute widget dimensions.
	fw,fh,asc,desc = self.TextExtent('Wg')
	self.frame_w = width
	self.frame_h = height - (fh*2)
	self.tick_y = height - desc - 2
	self.inner_w = self.frame_w - 2
	self.inner_h = self.frame_h - 2

	# Compute bar dimensions.
	self.area_h = self.inner_h
	self.bar_h = (self.area_h*2)/3
	top = (self.area_h-self.bar_h)/2

	# Set up the bar.
	texty = top + (self.bar_h/2) - (fh/2) + asc
	self.bar_value = 30.0
	self.bar_maxval = 50.0
	self.bar_top = top
	self.bar_texttop = top + (self.bar_h/2) - (fh/2) + asc
	self.bar_text = "%.2f" % self.bar_value

	# Allocate colors.
	self.red = self.MakeColor(.9,.2,.1)
	self.white = self.MakeColor(1,1,1)
	self.black = self.MakeColor(0,0,0)
	self.background = self.BackPen()
	self.foreground = self.ForePen()

    # Draw the bar.
    def draw_bars(self):
	x, y, h = 1, self.bar_top, self.bar_h
	w = (self.inner_w*self.bar_value)/self.bar_maxval
	self.ForePen(self.red)
	self.FillRectangle(x,y,w,h)
	if( w > 0 ):
	    self.ForePen(self.black)
	    self.DrawLine(x,y+h-1,x+w-1,y+h-1)
	    self.DrawLine(x+w-1,y,x+w-1,y+h-1)
	    self.ForePen(self.white)
	    self.DrawLine(x,y,x+w-1,y)
	    self.DrawLine(x,y,x,y+h-1)
	self.ForePen(self.foreground)
	self.DrawText(x+5,self.bar_texttop,self.bar_text,-1)
    
    # Draw some tick marks.
    def draw_scales(self):
	x, y, width, height = self.Dimensions()
	self.ForePen(self.background)
	self.FillRectangle(0,0,width,height)

	# Place the tick marks
	self.tick_marks = []
	for i in range(10):
	    text = '%.0f' % ((i*self.bar_maxval)/(10-1))
	    tw,th,ta,td = self.TextExtent(text)
	    xpos = (i*self.frame_w)/(10-1) - 1
	    val = (i*self.bar_maxval)/(10-1)
	    tx = xpos - (tw/2)
	    if( tx < 0 ):
		tx = 0
	    elif( (tx+tw) > self.frame_w ):
		tx = self.frame_w - tw - 1
	    self.tick_marks.append( ( val, xpos, tx, text, ) )
	self.ForePen(self.foreground)
	for i in self.tick_marks:
	    self.DrawLine(i[1],0,i[1],self.frame_h-1)
	    self.DrawText(i[2],self.tick_y,i[3],-1)
	self.ForePen(self.black)
	self.DrawLine(0,self.frame_h-1,self.frame_w-1,self.frame_h-1)
	self.DrawLine(self.frame_w-1,0,self.frame_w-1,self.frame_h-1)
	self.ForePen(self.white)
	self.DrawLine(0,0,self.frame_w-1,0)
	self.DrawLine(0,0,0,self.frame_h-2)

    # Refresh object's on-screen representation.
    def redraw(self):
	self.draw_scales();
	self.draw_bars();
	self.Flush()

    # Process "Redraw" event.
    def OnRedraw(self,ev,x,y,w,h):
	self.redraw()

# Make our program externally visible.
__export__ = ['ihEmbed']

The width and height of an applet default to fill the full document window; supplying a WIDTH and/or HEIGHT attribute will override these defaults. For example, to create the above graphics applet with a height that is 3/4 of the document window's height, the following markup can be used:

<OBJECT CLASSID="embed_gfx.ipy" HEIGHT="75%">
</OBJECT>

If you have an iHTML-capable browser, you can take a look at the graphics example.

Example 3: Parsing Parameters

The next example shows how to parse an applet's parameters; it builds on the previous example to display a vertical barchart. Parameters are handed to the applet as a normal Python dictionary; they can be examined and manipulated with all the familiar dictionary operations.

# A simple barchart.
#
# Recognizes the following parameters:
#
# Bars:  a tuple of values for each of the bars, e.g. (1.0, 2.3, 5.3).
# Ticks: number of tick-marks across the graph.

# Get the standard modules we need:
import ihApp    # Interactive HTML application interface
import string   # Python string operations

# Our own application is a subclass of the iHTML-defined
# Application class.
class ihEmbed(ihApp.Application):

    # Class initialization.
    def __init__(self,**args):

	# Let superclass initialize itself.  This takes care of
	# interpreting the Width and Height parameters.
	apply(ihApp.Application.__init__,(self,),args)

	# Extract widget dimensions, as set by superclass.
	x, y, width, height = self.Dimensions()

	# Extract bar arguments
	if( args.has_key('Bars') ):
	    self.bars = eval(args['Bars'])
	    if( type(self.bars) != type(()) ):
		raise TypeError,'"Bars" parameter must be a tuple'
	if( args.has_key('Ticks') ):
	    self.ticks = string.atof(args['Ticks'])
	else:
	    self.ticks = 10

	# Find font dimensions and use to help compute widget dimensions.
	fw,fh,asc,desc = self.TextExtent('Wg')
	self.frame_w = width
	self.frame_h = height - (fh*2)
	self.tick_y = height - desc - 2
	self.inner_w = self.frame_w - 2
	self.inner_h = self.frame_h - 2

	# Compute bar dimensions.
	self.num = len(self.bars)
	self.area_h = self.inner_h / self.num
	self.bar_h = (self.area_h*2)/3
	self.maxval = 0.0
	top = (self.area_h-self.bar_h)/2

	# Set up an array to hold the bar state.  This is an array of
	# arrays -- each array in it corresponds to one bar, and has
	# the form:  [ barval, topy, texty, label ], where
	# "barval" is the bar's value as given in the parameters,
	# "topy" is the y pixel location of the top of the bar,
	# "texty" is the y pixel location of the bar's text, and
	# "label" is a string representing the bar's textual label.
	self.current = []  # 
	for i in self.bars:
	    texty = top + (self.bar_h/2) - (fh/2) + asc
	    self.current.append( [i, top, texty, "%.2f" % i] )
	    if( self.maxval < i ):
		self.maxval = i
	    top = top + self.area_h
	if( self.maxval <= 0 ):
	    self.maxval = 1.0

	# Allocate colors.
	self.red = self.MakeColor(.9,.2,.1)
	self.white = self.MakeColor(1,1,1)
	self.black = self.MakeColor(0,0,0)
	self.background = self.BackPen()
	self.foreground = self.ForePen()

    # Draw the bars at their current positions -- draw the information
    # in the 'current' array.
    def draw_bars(self):
	for i in self.current:
	    x, y, h = 1, i[1], self.bar_h
	    w = (self.inner_w*i[0])/self.maxval
	    self.ForePen(self.red)
	    self.FillRectangle(x,y,w,h)
	    if( w > 0 ):
		self.ForePen(self.black)
		self.DrawLine(x,y+h-1,x+w-1,y+h-1)
		self.DrawLine(x+w-1,y,x+w-1,y+h-1)
		self.ForePen(self.white)
		self.DrawLine(x,y,x+w-1,y)
		self.DrawLine(x,y,x,y+h-1)
	    self.ForePen(self.foreground)
	    self.DrawText(x+5,i[2],i[3],-1)
    
    # Draw the barchart's tick marks.
    def draw_scales(self):
	x, y, width, height = self.Dimensions()
	self.ForePen(self.background)
	self.FillRectangle(0,0,width,height)

	# Place the tick marks
	self.tick_marks = []
	for i in range(self.ticks):
	    text = '%.0f' % ((i*self.maxval)/(self.ticks-1))
	    tw,th,ta,td = self.TextExtent(text)
	    xpos = (i*self.frame_w)/(self.ticks-1) - 1
	    val = (i*self.maxval)/(self.ticks-1)
	    tx = xpos - (tw/2)
	    if( tx < 0 ):
		tx = 0
	    elif( (tx+tw) > self.frame_w ):
		tx = self.frame_w - tw - 1
	    self.tick_marks.append( ( val, xpos, tx, text, ) )
	self.ForePen(self.foreground)
	for i in self.tick_marks:
	    self.DrawLine(i[1],0,i[1],self.frame_h-1)
	    self.DrawText(i[2],self.tick_y,i[3],-1)
	self.ForePen(self.black)
	self.DrawLine(0,self.frame_h-1,self.frame_w-1,self.frame_h-1)
	self.DrawLine(self.frame_w-1,0,self.frame_w-1,self.frame_h-1)
	self.ForePen(self.white)
	self.DrawLine(0,0,self.frame_w-1,0)
	self.DrawLine(0,0,0,self.frame_h-2)

    # Refresh object's on-screen representation.
    def redraw(self):
	self.draw_scales();
	self.draw_bars();
	self.Flush()

    # Process "Redraw" event.
    def OnRedraw(self,ev,x,y,w,h):
	self.redraw()

# Make our program externally visible.
__export__ = ['ihEmbed']

Arguments are supplied to the applet with the <PARAM> tag; here, the applet recognizes two parameters:

Bars
A Python tuple containing the values for each bar; the length of the tuple determines the total number of bars that are displayed.
Ticks
The number of tick marks to display across the barchart. If not specified, the applet defaults to 10 ticks.

For example, to create a barchart that is the full width of the document window, 3/4 of the document window's height, and has four bars with values 30, 70, 10, and 50, the following markup can be used:

<OBJECT CLASSID="embed_params.ipy" HEIGHT="75%">
<PARAM NAME="Bars" VALUE="(30.0,70.0,10.0,50.0)">
</OBJECT>

If you have an iHTML-capable browser, you can take a look at the parameter example.

Example 4: Animation

Below is our final example, which adds animation to the previous applet. This introduces three important new concepts:

Events

An applet receives information about user interaction through the Event class. Instances of this class are sent to the applet's OnEvent() method; by default, this methods distributes events to various higher-level methods in the widget, such as OnMouse(), OnKeyPress(), OnResize(), etc.

Double-buffering

In order to do smooth animation, it is usually necessary to draw into an off-screen buffer, and then display each frame only after it is complete. The Widget includes a method called MakePixmap() that allows an off-screen buffer to be created; pixmap objects support all of the standard drawing methods.

Future calls

A "future call" is a method call that will happen at some future time. It is created with the FutureCall() method; it is used here to control the time that elapses between each frame of the animation.

# An animated bar-graph.
#
# Recognizes the following parameters:
#
# Bars:  a tuple of values for each of the bars, e.g. (1.0, 2.3, 5.3).
# Steps: the number of frames in the bar animation.
# Rate:  second between each frame of the animation.
# Ticks: number of tick-marks across the graph.

# Get the standard modules we need:
import ihEvent  # Interactive HTML events
import ihApp    # Interactive HTML application interface
import string   # Python string operations

# Our own application is a subclass of the iHTML-defined
# Application class.
class ihEmbed(ihApp.Application):

    # Class initialization.
    def __init__(self,**args):

	# Let superclass initialize itself.  This takes care of
	# interpreting the Width and Height parameters.
	apply(ihApp.Application.__init__,(self,),args)

	# Allocate colors.
	self.red = self.MakeColor(.9,.2,.1)
	self.white = self.MakeColor(1,1,1)
	self.black = self.MakeColor(0,0,0)
	self.background = self.BackPen()
	self.foreground = self.ForePen()

	# Extract widget dimensions, as set by superclass, and allocate
	# rendering buffer.
	x, y, width, height = self.Dimensions()
	self.buffer = self.MakePixmap(width,height)
	self.buffer.ForePen(self.background)
	self.buffer.FillRectangle(0,0,width,height)

	# Extract bar arguments
	if( args.has_key('Bars') ):
	    self.bars = eval(args['Bars'])
	    if( type(self.bars) != type(()) ):
		raise TypeError,'"Bars" parameter must be a tuple'

	# Extract update arguments
	if( args.has_key('Steps') ):
	    self.steps = string.atoi(args['Steps'])
	else:
	    self.steps = 50
	if( args.has_key('Rate') ):
	    self.rate = string.atof(args['Rate'])
	else:
	    self.rate = 0.05
	if( args.has_key('Ticks') ):
	    self.ticks = string.atof(args['Ticks'])
	else:
	    self.ticks = 10

	# Find font dimensions and use to help compute widget dimensions.
	fw,fh,asc,desc = self.TextExtent('Wg')
	self.frame_w = width
	self.frame_h = height - (fh*2)
	self.tick_y = height - desc - 2
	self.inner_w = self.frame_w - 2
	self.inner_h = self.frame_h - 2

	# Compute bar dimensions.
	self.num = len(self.bars)
	self.area_h = self.inner_h / self.num
	self.bar_h = (self.area_h*2)/3
	self.maxval = 0.0
	top = (self.area_h-self.bar_h)/2

	# Set up an array to hold the bar state.  This is an array of
	# arrays -- each array in it corresponds to one bar, and has
	# the form:  [ barval, curval, topy, width, texty, label ], where
	# "barval" is the bar's value as given in the parameters,
	# "curval" is the current value being displayed,
	# "topy" is the y pixel location of the top of the bar,
	# "width" is the current width of the bar in pixels,
	# "texty" is the y pixel location of the bar's text, and
	# "label" is a string representing the bar's textual label.
	self.current = []  # 
	for i in self.bars:
	    texty = top + (self.bar_h/2) - (fh/2) + asc
	    self.current.append( [i, 0.0, top, 0.0, texty, "%.2f" % i] )
	    if( self.maxval < i ):
		self.maxval = i
	    top = top + self.area_h
	if( self.maxval <= 0 ):
	    self.maxval = 1.0

	# Set number of steps...
	self.curstep = 0

	# Start the animation.
	self.update(1)

    # This method is called to render the animation.  If 'restart' is
    # true, the animation is reset to its first frame; otherwise, the
    # next frame in sequence is drawn.
    def update(self,restart):
	if( restart ):
	    self.curstep = 0
	    self.draw_scales()

	# Set up for the next redraw: if in the middle of the animation,
	# do the next frame; otherwise, wait a while and then start over.
	if( self.curstep < self.steps ):
	    self.call = self.FutureCall(self.rate,self.update,(0,))
	else:
	    self.call = self.FutureCall(10,self.update,(1,))

	# Go to next step.
	cur = self.curstep + 1
	self.curstep = cur

	# Update the bar positions.
	for i in self.current:
	    i[1] = (i[0]*cur)/self.steps
	    i[3] = (self.inner_w*i[1])/self.maxval

	# Draw the bars.
	self.draw_bars()

	# Show the new frame to the user.
	self.redraw()

    # Draw the bars at their current positions -- draw the information
    # in the 'current' array into the off-screen buffer.
    def draw_bars(self):
	for i in self.current:
	    x, y, w, h = 1, i[2], i[3], self.bar_h
	    self.buffer.ForePen(self.red)
	    self.buffer.FillRectangle(x,y,w,h)
	    if( w > 0 ):
		self.buffer.ForePen(self.black)
		self.buffer.DrawLine(x,y+h-1,x+w-1,y+h-1)
		self.buffer.DrawLine(x+w-1,y,x+w-1,y+h-1)
		self.buffer.ForePen(self.white)
		self.buffer.DrawLine(x,y,x+w-1,y)
		self.buffer.DrawLine(x,y,x,y+h-1)
	    self.buffer.ForePen(self.foreground)
	    self.buffer.DrawText(x+5,i[4],i[5],-1)
    
    # Draw the barchart's tick marks into the off-screen buffer.
    def draw_scales(self):
	x, y, width, height = self.Dimensions()
	self.buffer.ForePen(self.background)
	self.buffer.FillRectangle(0,0,width,height)

	# Place the tick marks
	self.tick_marks = []
	for i in range(self.ticks):
	    text = '%.0f' % ((i*self.maxval)/(self.ticks-1))
	    tw,th,ta,td = self.buffer.TextExtent(text)
	    xpos = (i*self.frame_w)/(self.ticks-1) - 1
	    val = (i*self.maxval)/(self.ticks-1)
	    tx = xpos - (tw/2)
	    if( tx < 0 ):
		tx = 0
	    elif( (tx+tw) > self.frame_w ):
		tx = self.frame_w - tw - 1
	    self.tick_marks.append( ( val, xpos, tx, text, ) )
	self.buffer.ForePen(self.foreground)
	for i in self.tick_marks:
	    self.buffer.DrawLine(i[1],0,i[1],self.frame_h-1)
	    self.buffer.DrawText(i[2],self.tick_y,i[3],-1)
	self.buffer.ForePen(self.black)
	self.buffer.DrawLine(0,self.frame_h-1,self.frame_w-1,self.frame_h-1)
	self.buffer.DrawLine(self.frame_w-1,0,self.frame_w-1,self.frame_h-1)
	self.buffer.ForePen(self.white)
	self.buffer.DrawLine(0,0,self.frame_w-1,0)
	self.buffer.DrawLine(0,0,0,self.frame_h-2)

    # Refresh object's on-screen representation: copy the current
    # off-screen buffer to the screen.
    def redraw(self):
	self.Paste(0,0,self.buffer)
	self.Flush()

    # Process "Redraw" event.
    def OnRedraw(self,ev,x,y,w,h):
	self.redraw()

    # Process mouse events: restart animation if button is pressed.
    def OnMouse(self,ev,x,y,state,hit):

	if( ev.Code == ihEvent.CodeNames['MousePress'] ):
	    if( self.call ):
		self.call.Stop()
	    self.update(1)

# Make our program externally visible.
__export__ = ['ihEmbed']

If you have an iHTML-capable browser, you can take a look at the complete animation example.


[Home] [ToC] [Prev] [Next]

_________.oo_Q_Q_oo.____________________________________________
Dianne Kyra Hackborn <hackbod@angryredplanet.com>
Last modified: Thu Oct 17 21:41:40 PDT 1996

This web page and all material contained herein is Copyright (c) 1997 Dianne Hackborn, unless otherwise noted. All rights reserved.