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.
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.
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.
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:
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.
Below is our final example, which adds animation to the previous applet. This introduces three important new concepts:
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.
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.
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.
Dianne Kyra Hackborn <hackbod@angryredplanet.com> | Last modified: Thu Oct 17 21:41:40 PDT 1996 |